Drupal - Installation Guide

Page 1

Installation guide Drupal provides an installation script that automatically populates database tables and configures the correct settings in the settings.php file. This section covers preparing for installation, running the installation script itself, and the steps that should be done after running the installation script has completed. It also explains how to do a "multi site" installation, where a number of different Drupal sites run off the same code base. Before proceeding with your first Drupal installation, you should also review the best practices section. For help with Drupal terms, see the terminology page.

Other tools Some of the steps in the installation process can be performed with tools such as graphical applications for moving files and managing databases or tools that are provided by your hosting service. This documentation focuses on performing tasks at the command line. For information on using other tools, see the documentation that accompanies the application or is provided by your hosting service.

Creating a test site on a local computer It is considered a good practice to do all development work on a separate test site before making changes to a production site. A test site allows you to evaluate the impact of upgrades, new modules, modifications to themes etc. without causing disruption to your live site. For information about setting up a web server on a local computer, see the Local Server Setup section of the Developing for Drupal guide.

Alternative methods for installation Some web hosting companies offer "one-click" installations of Drupal, or specific Drupal support. You may be able to locate one on the Drupal hosting handbook page. There is also a handbook page listing Drupal distributions, which include installation profiles and pre-packaged distributions of Drupal and modules. These may be of help as well.

See also The troubleshooting FAQ.

System requirements Note: if you meet these requirements but still have problems with your site, be sure to read through the Webhosting Troubleshooting FAQ.

Resources A minimum base installation requires at least 3MB of disk space but you should assume that your actual disk space will be somewhat higher. For example, if you install many contributed modules and contributed themes, the actual disk space for your installation could easily be 40 MB or more (exclusive of database content, media, backups and other files). A useful FAQ (http://drupal.org/node/59680) explains how to use phpinfo to get the details of your system. For example, phpinfo will tell you if you have a database already installed and what version your system is running. Phpinfo will also tell you what php variables are set as well as many other helpful things.

Web server Drupal has been deployed successfully on both Apache and IIS. Apache (Recommended) Drupal will work on Apache 1.3 or Apache 2.x hosted on UNIX/Linux, OS X, or Windows. The majority of Drupal development and deployment is done on Apache, so there is more community experience and testing performed on Apache than on other web servers. You can use the Apache 'mod_rewrite' extension to allow for clean URLs. Microsoft IIS Drupal core will work using IIS 5, IIS 6, or IIS 7 if PHP is configured correctly. To achieve clean URLs you may need to use a third party product. For IIS7 you can use the Microsoft URL Rewrite Module or a third party solution. When using Drupal on IIS 7 with fastcgi you must install Hotfix kb954946, or wait until the hotfix appears in a package update (recommended). KB954946 was included in Windows 2008 Server SP2 Drupal is being developed to be web server independent, but we have limited or no reports of successful use on web servers not listed here.


The total file size of your Drupal installation will depend on what you add to your site, but Drupal core files alone will take up approximately 2 to 3 MB uncompressed. The exact size depends on the version of Drupal you have installed.

Database server Recommended: MySQL 4.1 or MySQL 5.0 Drupal 5.x and earlier supports MySQL 3.23.17 or higher. MySQL 4.1 or higher is strongly recommended. Drupal 6 supports MySQL 4.1 or higher. Drupal 7 will only support MySQL 5.0.15 or higher, and requires the PDO database extension for PHP (see PHP section below). NOTE: Drupal makes use of some features not available on some inexpensive hosting plans so please check that your host allows database accounts with the following rights: SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER. These rights are sufficient to run Drupal core 6.x. Some contributed modules, and also Drupal core 5.x (but not Drupal core 6.x), additionally require the following rights: CREATE TEMPORARY TABLES, LOCK TABLES. Note: If your system/host is running MySQL 4.1 or newer and you receive the error "Client does not support authentication protocol requested by server", address the problem by following the instructions provided by MySQL AB. There is a minor OS issue with some MySQL 5+ installations primarily on Windows but affecting some versions of Unix/Linux as well. Note: When using Drupal 5.x or later, particularly with contributed modules, it may be necessary to set the system variable max_allowed_packet to at least 16M. Some inexpensive hosting plans set this value too low (the MySQL default is only 1M). In that case, you may need to choose a better hosting plan. A value of 1M may be sufficient for 5.x. Note: Drupal supports MyISAM and InnoDB table types. NDB tables (MySQL Cluster) are not supported. Note that if your web hosting account is set up with a graphic control panel such as Plesk or CPanel, it is very likely that you do not need to worry about installing a driver for MySQL -- it is probably already installed on your server. You might wish to simply create your database and proceed with installing Drupal, and then refer back to Drupal documentation for specific troubleshooting help if you run into problems. PostgreSQL 7.4 or higher Note: Some contributed modules are not as abstracted from MySQL-specific code as everyone would like. If you are familiar with PostgreSQL please file issues with those contributed modules as you find them. Drupal 7 will only support PostgreSQL 8.3 or higher PHP 5.2.6 for Windows has a bug in its pgsql extension. You will need to replace it with the php_pgsql.dll from version 5.2.5. Currently Microsoft SQL Server and Oracle are not supported, but various efforts are underway to supply schema. Please see discussions in the Enterprise Group if you are interested in working on this.

PHP Recommended: PHP 5.2.x Required: PHP version 4.4.0 or higher (Contributed modules may not support this version of PHP) PHP 5.3 is not yet supported by Drupal 5.x, but is supported by Drupal 6.14 core and higher (see the release notes for 6.14) and also by Drupal 7.x. Note that some contributed modules may not be compatible with PHP 5.3, and that some PHP 5.3 configurations still show warnings with Drupal 6.14; work is ongoing to resolve the latter in #360605: PHP 5.3 Compatibility. PHP 5.2 or higher will be a requirement for Drupal 7. PHP memory requirements can vary significantly depending on your use of modules. While 16 MB may be sufficient for a default Drupal 6 installation and 30 MB may be sufficient for a default Drupal 7 installation, a production site with a number of commonly used modules enabled (CCK, Views etc.) could require 64 MB or more. Some installations may require much more, especially with media-rich implementations. If you are using a hosting service it is important to verify that your host can provide sufficient memory for the set of modules you are deploying or may deploy in the future. (See the Increase PHP memory limit page in the Troubleshooting FAQ for additional information on modifying the PHP memory limit.) The PHP extension for connecting to your chosen database must be installed and enabled. Drupal's currently supported database connectors are: mysql (the original MySQL extension), mysqli (an improved connector for newer MySQL installations), and pgsql (for PostgreSQL). Note: PHP 5.x no longer enables the mysql extension by default. Please read the links above for installing and enabling your chosen connector. Additionally, Drupal 6.x does not provide the option to select the mysql connector if mysqli is enabled in your PHP configuration. PHP XML extension (for Blog API, Drupal, and Ping modules). This extension is enabled by default in a standard PHP installation; the Windows version of PHP has built-in support


for this extension. An image library for PHP such as the GD library is needed for image manipulation (resizing user pictures, image and imagecache modules). GD is included with PHP 4.3 and higher and enabled by default. ImageMagick is also supported for basic image manipulations in Drupal core but there is much less support from contributed modules. PHP needs the following configuration directives for Drupal to work (only directives that differ from the default php.ini-dist / php.ini-recommended): register_globals: off; this is the default value, but some hosts have it enabled error_reporting set to E_ALL & ~E_NOTICE. Work is ongoing to change this to E_ALL for Drupal 6 and Drupal 7. safe_mode: off. Safe mode may interfere with file and image uploads. Php Data Objects (PDO) must be activated for Drupal 7 to install and run correctly. Look in your php.ini. Uncomment (remove the leading semicolin) at line extension=php_pdo.dll, extension=php_pdo_mysql.dll. If these lines are not there, you will need to add them. You can also use pecl install pdo to install pdo, and then pecl install pdo_mysql (for instance), to install the PDO database driver. More information can be found on the What is PDO page. In addition, we recommend the following setting: session.cache_limiter: nocache Some of these settings are contained in the default .htaccess file that ships with Drupal, so you shouldn't need to set them explicitly. Note, however, that setting PHP configuration options from .htaccess only works under the following conditions: With Apache (or a compatible web server) If the .htaccess file is actually read, i.e. AllowOverride is not None If PHP is installed as an Apache module See the PHP manual for how to change configuration settings for other interfaces to PHP. Drupal 7 may require the time parameter to be at least 30 seconds. In some shared hosting environments, access to these settings is restricted. If you cannot make these changes yourself, please ask your hosting provider to adjust them for you.

Browser requirements Websites built using just Drupal core (i.e. with no additional, contributed modules) are compatible with, and fully functional, in all modern browsers that support CSS and JavaScript. However, browsers have varying levels of compliance with Internet standards such as CSS 2, so there may be minor variations in appearance. Here is an incomplete list of browsers that are known to work well with Drupal core and support all of its features: Internet Explorer 6.x and later Firefox 2.x and later Opera 7 and later Safari 1.x and later Camino 1.x and later Google Chrome It is also possible to use a browser that does not support JavaScript with Drupal, although the functionality will of course be slightly different. For instance, in Drupal 6 running in a browser with JavaScript enabled, you can use a drag-and-drop interface to position blocks on the Blocks administration page (admin >> site building >> blocks). If you don't have JavaScript, you will still be able to position blocks, but you will use an interface more like Drupal 5, where you will assign numerical weights to the blocks. It is also possible to use a browser that does not support CSS with Drupal, but of course the site will not look very similar to how it looks in a browser that does support CSS. Some contributed modules and themes may not be compatible with all browsers. If you find a problem with browser compatibility in a contributed module or theme, or some functionality in a contributed module that does not work at all without JavaScript enabled, please submit an issue to report it to the module or theme maintainer.

Known Issues It's worth mentioning here that IE6 has an problem with loading more than 30 stylesheets, and it's fairly easy to run into this problem once you start adding contrib modules.

Capacity Planning There are no definite figures available for Drupal's resource requirements. They will vary depending on what modules you have installed, how many users you have, and what proportion log in rather than browse anonymously. In the absence of definite information, here are some relevant discussions: Need suggested baselines for Server Resource Requirements It would help to know baseline requirements for Drupal and just the Core Modules: - Simultaneous MySQL Connections - Simultaneous PHP Connections - CPU Thresholds


- CPU Thresholds The following discussions state: "Drupal regularly makes more than 100 queries per page load" (Does 1 query = 1 connection?) http://drupal.org/node/190070 Some combinations of modules and options have a big impact and would be worth measuring in D7. Menu entries expanded for drop down menus + path aliases + logged in users in D6: http://drupal.org/node/625898 "Executed 595 queries in 207 milliseconds." "Is it reasonable to expect 100s of simultaneous connections" http://drupal.org/node/312868

Measuring stuff The Devel module, http://drupal.org/project/devel, counts database calls and execution times. Firefox Firebug measures and displays the download times within the browser. See the overhead of adding Google analytics to a page. (for many sites, it is the slowest download.) See the overhead of splitting a CSS file into two files for easier maintenance. See the time saved by Drupal CSS and Javascript merging. Measure your Drupal 6 configuration and modules to see what has the greatest impact on performance then look in the issues to see if there are major changes for the D7 versions. Measure for both anonymous users and logged in users. A feature might add hundreds or thousands of database calls. That extra overhead might occur once every 5 minutes for anonymous users and once every page for logged in users. Remeasure after adding a module. When you add a cloud display module, you might add a read of every node for every display. You might find an alternative module that performs the reads less frequently and caches the result. Remeasure after changing a setting. An example: Adding path aliases in Drupal 6 had the side effect of adding a database call for every entry in the menu, a significant difference for logged in users. Combine that with hidden entries behind drop down menus and you could have a big overhead. Now do something as simple as listing your main menu and select Expanded for items with a lot of children. You could be adding hundreds of menu entries to an otherwise small small menu. A logged in user will get a fresh menu on every page view. Remeasure after changing themes. Some themes add their own file and database accesses. Run Firebug to compare the CSS and Javascript file downloads, especially if the theme creates problems with CSS or Javascript merging. Check known Drupal 6 performance problems in D7. Some parts of D7 are different and some parts hardly changed. If you know of something that had a big impact in D6 then check it in D7. Where the impact is similar, either document the problem or open an issue for possible improvement. Check the impact of Fields in your favourite add on modules. An add on module in D6 might add a special table with one row per node. In D7 the same module might add a field to the existing node fields. The change from separate tables to a single table might make things faster by reducing accesses. The change might also slow things down because the separate table uses an index for a lookup while the merged table uses a standard row by row search. It is worth reporting the results, both positive and negative, to the module developers.

What is PDO? PDO is an acronym for PHP Data Objects. PDO is a lean, consistent way to access databases. This means developers can write portable code much easier. PDO is not an abstraction layer like PearDB. PDO is a more like a data access layer which uses a unified API (Application Programming Interface).

How to enable PDO To enable PDO configure --enable-pdo and --with-pdo_sqlite --with_pdo_mysql or whatever database needs supporting by PDO.

Windows users For Apache, you will need to make sure php_pdo.dll and php_pdo_mysql.dll exist in the php/ext directory, un-comment or add the appropriate lines in php.ini, and restart the web server. For IIS, PDO DLLs are not enabled by default. The preferred method for enabling them is to go to the Control Panel | Add/Remove Programs, highlight your PHP installation and click "Change" (Change/Remove - XP). Specify "FastCGI", then modify the installed extensions to include these two, then restart your server.


Macintosh users Method 1 OS X 10.5: Detailed, step-by-step instructions are available here: Getting PHP + GD + pdo_mysql working on OSX 10.5 (aka recompiling everything) Getting PHP + GD + PostgreSQL working on OSX 10.5 (aka recompiling everything) OS X 10.6: OS 10.6 comes with the PDO extension enabled by default. (For instructions on configuring your development environment, see: Drupal 6 on OS X 10.6 Method 2 MAMP comes pre-configured with the PDO extension. For more information, see: HowTo: Create a local environment using MAMP.

Linux users PDO is enabled by default as of php 5.1.0 on most linux systems. There is documentation to enable PDO for a mysql specific-driver. Ubuntu As with most Linux systems, PDO support is present in PHP5 in all recent Ubuntu distributions, and certainly in PHP 5.2 which is required for Drupal 7. If you find that PDO support is not enabled, install the following packages and restart the server. sudo apt-get install php5-common php5-mysql sudo /etc/init.d/apache2 reload

Before restarting the server, make sure that extension=pdo.so and extension=pdo_mysql.so are being loaded, either in the php.ini file or in their own .ini files inside /etc/php5/conf.d. Ubuntu PDO Troubleshooting

Do not use the PECL PDO installer. The project is horribly outdated and abandoned. (See http://pecl.php.net/package/PDO.) Cases have been reported where PECL PDO support seems enabled but it doesn't work correctly. One symptom was the inability to install Drupal 7 alpha4 or newer. If you have already installed PECL PDO, you will need to remove it and then reinstall the following PHP packages, because PHP's PDO drivers have probably been overwritten. sudo sudo sudo sudo

pecl uninstall pdo_mysql pecl uninstall pdo apt-get install --reinstall php5-common php5-mysql /etc/init.d/apache2 reload

Again. before restarting the server, make sure that extension=pdo.so and extension=pdo_mysql.so are being loaded, either in the php.ini file or in their own .ini files inside /etc/php5/conf.d

Basic installation Quick install for experts This page provides a summary of the command line instructions for installing Drupal on a typical UNIX/Linux web server. Every step contains a link to more detailed installation instructions where you also can find information about installing Drupal on other systems. If you have problems, read Troubleshooting common problems. Read more about system requirements (among which detailed PHP-settings and browser requirements).

1. Download and extract files Download Drupal from http://drupal.org/project/drupal and extract the files with a compression tool (such as tar or 7-zip). wget http://drupal.org/files/projects/drupal-x.x.tar.gz tar -zxvf drupal-x.x.tar.gz

Move the files to a directory within your web server's document root or your public HTML directory using the following command, substituting drupal-x.x with the actual version number. (On many *nix computers the path from the server's root will be /var/www/.) mv drupal-x.x /var/www/


You can install and use Drupal in other languages by downloading translations from http://drupal.org/project/translation. Extract the files into the Drupal directory (e.g. /var/www/drupal-x.x) Read more about downloading and extracting Drupal (includes instructions for FTP, Windows and Mac OS).

2. Create the configuration file and grant permissions In the sites/default directory, copy the default.settings.php file and name the copied file as settings.php. cp sites/default/default.settings.php sites/default/settings.php

Give the web server write privileges ( 666 or u=rw,g=rw,o=rw) to the configuration file. chmod a+w sites/default/settings.php

Give the web server write privileges to the sites/default directory. chmod a+w sites/default

Read more about preparing the configuration file.

3. Create the Drupal database To complete the installation, you must create a database for Drupal to use. You can do this by command line or through phpMyAdmin (or another web interface). mysqladmin -u username -p create databasename

Where 'username' is a MySQL user which has the CREATE and GRANT privileges. MySQL will prompt for the 'username' database password. Next you must set the access database rights. Log in to MySQL: mysql -u username -p

At the MySQL prompt, enter: GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES ON databasename.* TO 'username'@'localhost' IDENTIFIED BY 'password';

Where: 'databasename' is the name of your database 'username' is the username of your MySQL account 'localhost' is the server name used to access MySQL 'password' is the password required for that username If successful, MySQL will reply with: Query OK, 0 rows affected

Read more about creating the database (among which instructions on using phpMyAdmin, and PostgreSQL and SQLite as database).

4. Run the installation script To run the installation script, point your browser to the base URL of your website (e.g. http://www.example.com, http://www.example.com/drupal or http://localhost/drupal). The installation wizard will guide you through several screens to set up the database, add the first user account, and provide basic web site settings. Follow the wizard to finalize the installation and start working with your Drupal website. Read more about the installation script (along with a detailed description of all the available options).

Before you begin Drupal provides an installation script that automatically populates database tables and configures the correct settings in the settings.php file. This Installation Guide covers: Preparing to install Downloading software Modifying file permissions Running the installation script Troubleshooting problems Optional: Multi-site installation (where a number of different Drupal sites run off the same code base)

About this guide


This guide focuses on installing Drupal on a remote server running *nix (Linux, Unix, or similar) and performing tasks at the command line. You should have a terminal program and be comfortable using a text-based interface. If you do not have "shell access" (the ability to connect to the remote computer using the command line) to the remote server and/or if you would prefer to use an ftp client instead of command-line commands: See "Alternative tools" below. If you are installing Drupal on a Windows Server, see the "For Windows Users" notes in each section. For IIS installations, this guide assumes you have created a web site using the IIS Manager.

Helpful Info Before proceeding with your first Drupal installation, you should also review: Drupal requirements Web Hosting Issues Drupal best practices section Drupal terminology

Alternative tools Some of the steps in the installation process can be performed with tools such as graphical applications for moving files (File Transfer Protocol-FTP applications) or managing databases. This documentation focuses on performing tasks at the command line. For information on using other tools, see the documentation that accompanies the application or is provided by your hosting service. Some FTP information you might find helpful: How to install drupal for newbies using windows, FTP and phpMyAdmin Videocast of Drupal 6 installation Modifying Linux, Unix, and Mac file permissions

Creating a test site It is considered a good practice to do all development work on a separate test site before making changes to a production site. A test site allows you to evaluate the impact of upgrades, new modules, modifications to themes etc. without causing disruption to your live site. For information about setting up a web server on a local computer, see the Local Server Setup section of the Developing for Drupal guide. A test site can also be set up on your web server using a separate domain or subdomain. For help setting up a test site running on the same codebase, see the [D6 Advanced and multisite installation guide]. You should also review Copying a live site via command line

Alternative methods for installation Some web hosting companies offer "one-click" installations of Drupal, or specific Drupal support. You may be able to locate one on the Drupal hosting handbook page. There is a handbook page listing Drupal distributions, which include installation profiles and prepackaged distributions of Drupal and modules. These may be of help as well.

Step 1: Download and uncompress Drupal Drupal is available in several versions. The "recommended release" is the latest stable release. To learn more about versions, see the Drupal version information page. Information about Drupal translations is available at http://drupal.org/project/translations.

Downloading Drupal Before you begin, log in to your server and navigate to the directory from which you will be serving your Drupal site. On many *nix computers the path from the server's root will be /var/www/html, so cd /var/www/html. On a shared server, or a server that hosts multiple domains, the path will be different -- perhaps the command cd ~/www or cd ~/public_html will work. If you are unsure of the directory, ask your hosting provider for assistance. Download Drupal using any download utility, the two most popular of which are wget and curl. Not every computer has both. The commands are: wget http://ftp.drupal.org/files/projects/drupal-x.x.tar.gz

or curl -O http://drupal.org/files/projects/drupal-x.x.tar.gz

(Note that the option for the curl command is the upper case letter "O" and not the numeral that stands for zero.)


(Replace the "http://drupal.org/files/projects/drupal-x.x.tar.gz" string with the link for the version you will be installing.)

Uncompressing Drupal Type the following command (replacing "x.x" with your downloaded version): tar -xzvf drupal-x.x.tar.gz

Remove the compressed file by typing rm drupal-x.x.tar.gz.

Moving Drupal to its intended location Now you need to move the contents of the drupal-x.x directory one level "up" into the web server's document root or your public HTML directory: mv drupal-x.x/* drupal-x.x/.htaccess ./

The files from the directory you downloaded and decompressed have now been moved up a level into your web directory, and you can delete the drupal-x.x directory (which is now empty): rmdir drupal-x.x

Before continuing to the next page ... The base URL for your Drupal installation will be set in your Web server's configuration file. You will need to know this URL before proceeding to the next steps of the installation. If you are installing Drupal on your local machine the base URL may be: http://localhost. If you are installing Drupal to a Web server your base URL may be a specific domain name (such as http://example.com).

OS-Specific Instructions For other operating systems besides *nix, refer to your site's operating system's page for instructions and notes.

Macintosh Download Notes If you are installing on a Mac server, or are creating a test site on your Mac, you may also want review these handbook pages during installation: 1. http://drupal.org/node/369107 2. http://drupal.org/node/22676 -- especially if you are unsure of the "web server's document root or the location of your public HTML directory

SELinux Download Notes Note for SELinux users Users of Fedora or other distributions with SELinux (Security Enhanced Linux) should not move files unpacked into their home directory into the Web directory /var/www/html.+++++WHY NOT JUST HAVE THEM DL TO THE WEB DIRECTORY & UNPACK THERE TO BEGIN WITH? (as per the DL instructions I just modified) I do not know anything about SELinux (unless my CentOS server *is* SELinux ... which statement reveals my utter ignorance) so could someone else please check this Note for SELinux users section? ty --kazar 2009-08-18+++++ As posted at http://drupal.org/node/50280 moving a file preserves the context with the directory in which it was created. In this case the files are incorrectly associated with the home directory ( user_home_t) instead of the web directory ( httpd_sys_content_t). Instead, copying the files to the directory /var/www/html will cause them to inherit the context of the correct directory: cp -R drupal-x.x/* drupal-x.x/.htaccess /var/www/html

If you have already moved the files you may re-associate the files using the command chcon to change the security context for the files: chcon -R -t httpd_sys_content_t /var/www/html

See: SELinux may cause mysterious permission problems

Windows Download Instructions Prerequisites Unzip program for processing .tar.gz files. .tar.gz is a format Windows doesn't understand


by default. This tutorial assumes you've downloaded and installed the freely available utility 7-Zip to allow you to extract .tar.gz files. A number of other file compression utilities are also available.

Download Drupal 1. At the project download page., find the version you want to download. In this case, select the first Drupal 7 version under the section 'Official Releases'. Click on 'Download'. Save the file (don't open it with another program.) 2. Uncompress the file. Right-click on the .tar.gz file and select Open With...|7-Zip. The final Drupal folder will appear. Hilight it, click Extract, and specify the folder into which you would like to extract it. Note: If you extract the files into a folder other than your web site's folder, copy the contents of the Drupal folder into the appropriate web folder, rather than cutting/pasting them. (This will ensure the files will inherit the appropriate permissions for the web server.) 3. Create the files folder. In the sites/default folder, create a folder called files and grant modify permissions for it to IIS_WPG (IIS6) or IIS_IUSRS (IIS7). 4. IIS7: Create/modify web.config file. Drupal distributions come with .htaccess files for *nix use; IIS7 users should convert this file to a web.config file for their site. If you have used the IIS Manager to create a new site, a basic web.config file will exist in your site's root directory. Edit the file to look like this example: <?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <!-- Don't show directory listings for URLs which map to a directory. --> <directoryBrowse enabled="false" /> <!-Caching configuration was not delegated by default. Some hosters may not delegate the caching configuration to site owners by default and that may cause errors when users install. Uncomment this if you want to and are allowed to enable caching --> <!-<caching> <profiles> <add extension=".php" policy="DisableCache" kernelCachePolicy="DisableCache" /> <add extension=".html" policy="CacheForTimePeriod" kernelCachePolicy="CacheForTimePeriod" duration="14:00:00" /> </profiles> </caching> --> <rewrite> <rules> <!-- rule name="postinst-redirect" stopProcessing="true"> <match url=".*" /> <action type="Rewrite" url="postinst.php"/> </rule --> <rule name="Protect files and directories from prying eyes" stopProcessing="true"> <match url="\.(engine|inc|info|install|module|profile|test|po|sh|.*sql|postinst.1|theme|tpl(\.php)? |xtmpl|svn-base)$|^(codestyle\.pl|Entries.*|Repository|Root|Tag|Template|allwcprops|entries|format)$" /> <action type="CustomResponse" statusCode="403" subStatusCode="0" statusReason="Forbidden" statusDescription="Access is forbidden." /> </rule> <rule name="Force simple error message for requests for nonexistent favicon.ico" stopProcessing="true"> <match url="favicon\.ico" /> <action type="CustomResponse" statusCode="404" subStatusCode="1" statusReason="File Not Found" statusDescription="The requested file favicon.ico was not found" /> </rule> <!-- To redirect all users to access the site WITH the 'www.' prefix, http://example.com/... will be redirected to http://www.example.com/...) adapt and uncomment the following: --> <!-<rule name="Redirect to add www" stopProcessing="true"> <match url="^(.*)$" ignoreCase="false" /> <conditions> <add input="{HTTP_HOST}" pattern="^example\.com$" /> </conditions> <action type="Redirect" redirectType="Permanent" url="http://www.example.com/{R:1}" /> </rule> --> <!-- To redirect all users to access the site WITHOUT the 'www.' prefix, http://www.example.com/... will be redirected to http://example.com/...) adapt and uncomment the following: --> <!-<rule name="Redirect to remove www" stopProcessing="true">


<match url="^(.*)$" ignoreCase="false" /> <conditions> <add input="{HTTP_HOST}" pattern="^www\.example\.com$" /> </conditions> <action type="Redirect" redirectType="Permanent" url="http://example.com/{R:1}" /> </rule> --> <!-- Rewrite URLs of the form 'x' to the form 'index.php?q=x'. --> <rule name="Short URLS" stopProcessing="true"> <match url="^(.*)$" ignoreCase="false" /> <conditions> <add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" /> <add input="{URL}" pattern="^/favicon.ico$" ignoreCase="false" negate="true" /> </conditions> <action type="Rewrite" url="index.php?q={R:1}" appendQueryString="true" /> </rule> </rules> </rewrite> <!-- httpErrors> <remove statusCode="404" subStatusCode="-1" /> <error statusCode="404" prefixLanguageFilePath="" path="/index.php" responseMode="ExecuteURL" /> </httpErrors --> <defaultDocument> <!-- Set the default document --> <files> <remove value="index.php" /> <add value="index.php" /> </files> </defaultDocument> </system.webServer> </configuration>

Step 2: Create the database Before running the installation script, you must create an empty database and database user (a user name assigned the rights to use the Drupal database). This page offers guidance for creating your Drupal database using a Web browser-based control panel (such as "CPanel" or "Plesk"), or by using phpMyAdmin (another browser-based database utility), or by creating the database directly from the MySQL or PostgreSQL command prompt (for more advanced users).

Create a database using a browser-based control panel Most web hosting accounts provide a Web-based control panel to help you administer your site. These tools include easy-to-use features for creating a new database, and for creating a "user" with rights to the database. To create a database using a browser-based control panel consult the documentation or ask your web host service provider. When you create the user for your database, you may see a page where you can specify the privileges that user will have for various operations on the database. In most Web control panels' "database wizard", if you simply check "All" privileges for the user you create (and then uncheck "Grant" if it is listed as a privilege) your user will be set up correctly. Take note of the username, password, database name and hostname (e.g., are you installing in www.example.com, or in drupal.example.com, www.example.com/blog etc.) as you create the database. You will enter these items into fields in your browser when running the install script (see next page). Note that in many cases when creating databases and users via a Web-based graphic interface, the user name you use to log into your control panel is added as a prefix to the database name and possibly to the database user name as well. For example, if you log into your Web site control panel as "webadmin" and create a database named "drupal7db" and a user for that database named "d7user", when running the Install script (see next page) the database and user may need to be typed in as "webadmin_drupal7db" and "webadmin_d7user". (This is because many hosting accounts are on shared servers, and on one server each database and user name must be unique across all accounts on the server.) If you have created your database and user via a Web-based interface, you can skip the remainder of this page and continue with the install instructions on the next page.

Create database and user using PHPMyAdmin This presumes you have root access to PHPMyAdmin 1. 2. 3. 4.

Log in to PHPMyAdmin as the root user Click Privileges & Add a new User In the User name field, enter the username you wish to use In the Host field, select Local which is more secure, unless you will be accessing the


database with this user from another server 5. Enter or generate a password for the user. 6. In the Database for User list, select Create database with same name and grant all privileges and click Go You have now created a user that has all privileges only on the database with the same name. This is more secure than using a general username & password for all your sites on the same server, as it limits access to your databases if someone gets hold of your database logins. If you want to have a different name for database and user, just select the created database, then operations tab. You will find an option Rename database to. If you need more details about using PHPMyAdmin, check out the official wiki. Take note of the username, password, database name and hostname (e.g., are you installing in www.example.com, or in drupal.example.com, www.example.com/blog etc.) as you create the database. You will enter these items into fields in your browser when running the install script (see next page). Note that in many cases when creating databases and users using a web interface, the user name you use to log in to your control panel is added as a prefix to the database name and possibly to the database user name as well. For example, if you log into your Web site control panel as "webadmin" and create a database named "drupal7db" and a user for that database named "d7user", when running the Install script (see next page) the database and user may need to be typed in as "webadmin_drupal7db" and "webadmin_d7user". (This is because many hosting accounts are on shared servers, and on one server each database and user name must be unique across all accounts on the server.) If you have created your database and user via phpMyAdmin, you can skip the remainder of this page and continue with the install instructions on the next page.

Create a database from the command line If you do not use a Web control panel or are experienced with and prefer to use MySQL or PostgreSQL commands, you can follow the information below. Additional information about privileges, and instructions to create a database using the command line are available in INSTALL.mysql.txt (for MySQL) or INSTALL.pgsql.txt (for PostgreSQL).

Create a database using MySQL commands For information on installing and configuring MySQL see http://dev.mysql.com/techresources/articles/mysql_intro.html In the following examples, 'username' is an example MySQL user who will have the CREATE and GRANT privileges and 'databasename' is the name of the new database Use the appropriate names for your system. 1. Create a new database for your site. mysqladmin -u username -p create databasename

MySQL prompts for the 'username' database password, and creates the initial database files. 2. Log in and set the access database rights: mysql -u username -p

MySQL prompts for the 'username' database password. 3. At the MySQL prompt, set the permissions using the following command: GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES, CREATE TEMPORARY TABLES ON `databasename`.* TO 'username'@'localhost' IDENTIFIED BY 'password';

In this case: 'databasename' is the name of your database 'username@localhost' is the username of your MySQL account 'password' is the password required for that username You will need the ` around the database name if you have used a MySQL escape character in your schema name. Eg: drupal_test_account.* should be drupal\_test\_account.* for security (underscore is a wildcard). This requires the ` wrapper. `drupal\_test\_account`.* Note: unless your database user has all of the privileges listed above, you will not be able to run Drupal. 4. If successful, MySQL will reply with: Query OK, 0 rows affected

5. To activate the new permissions, enter the following command: FLUSH PRIVILEGES;


The database should be created with UTF-8 (Unicode) encoding.

Create a database using PostgreSQL The database must be created with UTF-8 (Unicode) encoding. 1. Create a database user This step is only necessary if you don't already have a user setup (e.g. by your host) or you want to create new user for use with Drupal only. The following command creates a new user named 'username' (you should substitute this with the desired username), and prompts for a password for that user: createuser --pwprompt --encrypted --no-adduser --no-createdb username

If everything works correctly, you'll see a CREATE USER notice. 2. Create the database This step is only necessary if you don't already have a database setup (e.g. by your host) or you want to create new database for use with Drupal only. The following command creates a new database named "databasename" (you should substitute this with the desired database name), which is owned by previously created "username": createdb --encoding=UNICODE --owner=username databasename

If everything works correctly, you'll see a CREATE DATABASE notice.

Step 3: settings.php You must create a settings.php file and set some permissions.

Settings.php Your Drupal download comes with a sample configuration file at sites/default /default.settings.php. The default file is not used by the installer. It must be copied and the new file must be given the correct name. 1. Copy the default.settings.php to settings.php. You can do this from the command line (working from the root of the directory containing your Drupal installation) using cp sites/default/default.settings.php sites/default/settings.php. NOTE: Do not simply rename the file. The Drupal installer will need both files. 2. You should now have both a default.settings.php and settings.php file in your sites/default directory. 3. Make the settings file writeable, so that the installer can edit it, using chmod a+w sites/default/settings.php

or chmod 666 sites/default/settings.php

Both commands have the same effect. Several FTP tools like Filezilla, Transmit, and Fetch allow you to change file permissions, using a 'file attribute' or 'get info' command. In this case the octal or numeric value file permission should be set to 666. If your ftp client has checkboxes for setting permissions, check both the Read and Write boxes for "Owner", "Group", and "Others" (leaving the Execute boxes unchecked). 4. To let the files directory be created automatically, give the web server write privileges to the sites/default directory. chmod a+w sites/default

OS-Specific Instructions For other operating systems besides *nix, refer to your site's operating system's page for instructions and notes.

Fedora Linux You will need to put selinux in permissive mode to have the permissions modified by the install script. Even if you have the permissions set correctly for settings.php the install script will think you don't if selinux is active. You may need to turn off selinux during your Drupal install and turn it on after the install is complete. Remember that permissions must also be set to allow writing to the default folder so that the startup script can create the files folder inside it.

IIS (Windows)


On a Windows system using IIS, right-click on sites/default/settings.php and grant Modify permissions to IIS_WPG (IIS6) or IIS_IUSRS (IIS7). IIS7: You can also do this from the command line from within your \Drupal directory: C:\inetpub\wwwroot\Drupal>icacls sites\default\settings.php /grant BUILTIN\IIS_IUSRS:(M)

The installer will change the file back to Read Only after installation, but you should verify this after installation. For more information about modifying Windows file permissions, see the Troubleshooting FAQ.

Mac OS X Optional instructions on setting up a local MAMP (Mac Apache MySQL PHP) server. For an external server, follow these steps: 1. Install Drupal 7.0 as /Users/xxx/Sites/drupal, user xxx is in the "_www" group and make the entire installation is group writable. Create a soft link from /Library/WebServer/Documents/xxx to /Users/xxx/Sites. 2. Re-writing seems to be enabled by default in Apache2 on Leopard. Enable PHP5 and virtual hosts in Apache2, i.e. by uncommenting these lines in /etc/apache2/httpd.conf: #<br /># Dynamic Shared Object (DSO) Support<br />#<br />...<br /><br />LoadModule libexec/apache2/libphp5.so<br /><br />...<br /><br /># Virtual hosts<br />Include /private/etc/apache2/extra/httpd-vhosts.conf

3. Add a virtual host stanza to the /private/etc/apache2/extra/httpd-vhosts.conf file that has at least this in it: <VirtualHost *:80><br />    DocumentRoot /Library/WebServer/Documents<br />    ServerName localhost<br />    <br />    <Directory "/Library/WebServer/Documents"><br />        Options FollowSymLinks Indexes MultiViews<br />        AllowOverride All<br />        Order allow,deny<br />        Allow from all<br />    </Directory><br /></VirtualHost>

4. Add a variant of the RewriteBase /drupal line in the stock /Users/xxx/Sites/drupal/.htaccess file: <IfModule mod_rewrite.c><br /> RewriteEngine on<br /><br /> ...<br /><br /> # Modify the RewriteBase if you are using Drupal in a subdirectory or in a<br /> # VirtualDocumentRoot and the rewrite rules are not working properly.<br /> # For example if your site is at <a href="http://example.com/drupal" title="http://example.com/drupal" rel="nofollow">http://example.com/drupal</a> uncomment and<br /> # modify the following line:<br /> # RewriteBase /drupal<br /> RewriteBase /xxx/drupal<br /><br /> ...<br /><br /> # Rewrite URLs of the form 'x' to the form 'index.php?q=x'.<br /> RewriteCond %{REQUEST_FILENAME} !-f<br /> RewriteCond %{REQUEST_FILENAME} !-d<br /> RewriteCond %{REQUEST_URI} !=/favicon.ico<br /> RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]<br /></IfModule>

5. Make a copy of the /Users/xxx/Sites/drupal/sites/default directory tree as /Users/xxx/Sites/drupal/sites/localhost. Make sure this directory is writable by the _www group. 6. Manually create the database in MySQL. Make a copy of /Users/xxx/Sites/drupal/sites/localhost/default.settings.php file as /Users/xxx/Sites/drupal/sites/settings.php and make this file writable by the _www group. Configure the database in /Users/xxx/Sites/drupal/sites/settings.php /**<br /> * Database settings:<br /> *<br />...<br /><br /> */<br />#$db_url = 'mysql://username:password@localhost/databasename';

7. Rebuild PHP5 to include the GD tools according to the instructions here. 8. After modifying the file permissions in the next step, go to http://localhost/xxx/drupal/install.php. The pages came up as shown on the Run the install script.

Step 4: Run the installation script For Drupal 6 To run the install script point your browser to the base URL of your website. The base URL is defined in your Web server configuration file and is specific to the document


root where you placed your Drupal files. If you have installed Drupal on a Web server this will likely be a domain name such as http://example.com. If you have installed Drupal on your desktop machine this URL might be http://localhost.

You will be guided through several screens to set up the database, create tables,

add the first user account and provide basic web site settings.


If you get any errors regarding the files directory and its permissions, see [#394704]. For Drupal 7 To run the Drupal install script, point your browser to the base URL of your web site. The "base URL" means the document root (directory) where you placed your Drupal files (and is defined in your web server configuration file). If you have installed Drupal on a web host this will likely be a domain name such as http://www.example.com. If you installed Drupal in a subfolder, then you should point your browser to the subfolder (e.g. http://example.com/subfolder). If you have installed Drupal on your desktop machine this URL might be http://localhost/drupal. If the installation process does not simply appear by entering the base URL of your site, add the file name "install.php" (for example http://www.example.com/install.php). You will be guided through several screens: 1. Choose which profile to use for the installation (standard or minimal). Most people should select the "standard" option. The standard option comes with default content types already enabled, such as Article and Page, and with appropriate publishing options already set. (Of course you can later edit these default content types and their settings, or add additional ones.) The standard profile also has a useful collection of modules pre-enabled for you. The "minimal" option is targeted toward more experienced Drupal site creators who wish to set up their own content types with associated publishing options. The minimal profile has only three modules enabled: Block, Database logging, and Update status. 2. Select a language If you want to install using a language other than the default English, click the Learn how to install Drupal in other languages link. 3. Verify requirements If your installation directory is not yet configured properly, you will be informed at this step. You can correct the settings individually and either refresh the browser screen or click 'Try again' to see whether there are any errors left. Possible errors include: Missing directories and/or incorrect permissions The installer will attempt to automatically set up a number of directories, but this may fail due to permission settings. In this case you will find the missing directories listed.


sites/default/files sites/default/private sites/default/private/files

These directories should be set to the following permissions chmod o+w sites/default/files OR chmod 777 sites/default/files Missing settings.php or incorrect permissions If settings.php is missing or not accessible, follow the instructions in Step 2: Modify file permission. Note that you will need both the default.settings.php and settings.php files. 4. Set up database Enter the database name as well as the username and password for the database that you created in Step 3: Create Drupal's database. This username and password information allows Drupal to access your database, so the install script can create tables. Note that this is not the username and password for administering Drupal; these will be created in the next step. The Advanced options will allow you to change the database host ('localhost' is usually used in this entry: wamp/bin/apache/Apache2.2.11/bin/php.ini as an example of the location on a Windows box running WAMP). You can also change the port and the table prefix. You only need to change the port if you are using a non-standard port number. The table prefix is useful if you are installing multiple instances of Drupal tables that share the same database. Click Save and continue at the bottom of the page. 5. Install profile A progress bar will appear and display notes from the installer regarding the progress of the installation. If no errors are encountered, the next page will automatically load in your browser. 6. Configure site Input the information for the first user account (which will be automatically assigned full administration permissions) and provide basic web site settings. In the Site name field enter the name you wish to use for the site. You can also edit it later through the administration interface. In the Site e-mail address field, enter the e-mail address that will be used by Drupal when it sends out notifications such as registration information. In the Site maintenance account field, enter the Username, E-mail address, and password for the main administration account. Note that there is a distinction, as of Drupal 7, between the main administration account that you set up on this page, and the "Administrator" site administrator user role that you will see when you visit the "Roles" and "Permissions" pages in the administration interface. The account you set up in the Site maintenance account section during installation is a super-user who has overall control over every aspect of the management and configuration of the site. (This will be www.example.com/user/1, for those of you familiar with that account from earlier versions of Drupal.) In the Server settings field, select your Default country and Default time zone. In the Update notifications field leave both boxes checked if you want your Drupal server to alert you when updates are required. Often updates relate to security issues and are important to perform. However, if you have restricted Internet connectivity (for example if you are behind a corporate firewall) you may want to leave these settings unchecked and test them later. Click "Save and continue". On success you will see the Drupal installation complete screen. If there are any error messages, review and correct them now.

Secure your site After the installation is complete, remove write permissions for the settings.php file. chmod u=rw,o=r,a=r sites/default/settings.php sites/default/settings.php

OR chmod 644

If you make manual changes to the settings.php file later, be sure to protect it again after making your modifications. Failure to remove write permissions to that file is a security risk. (Although the default location for the settings.php file is at sites/default/settings.php, it may be in another location if you use the multi-site setup.)

Installing an installation profile What is an installation profile? From Installation profiles: Installation profiles are a feature in Drupal core that was added in the 5.x series. The Drupal installer allows you to specify an installation profile which defines which modules should be enabled, and can customize the new installation after they have been installed. This will allow customized "distributions" that enable and configure a


set of modules that work together for a specific kind of site (Drupal for bloggers, Drupal for musicians, Drupal for developers, and so on). Please note that an installation profile can only be used when you're installing a new Drupal instance. This means that you can not run an installation profile on an existing Drupal to add extra functionality. You can also only select one installation profile, it is not possible to install a wiki installation profile along with a French language profile for instance. The installation of an installation profile is not required, but optional.

Installing the profile 1. Start by downloading the installation profile and unpacking it. 2. Copy the entire directory that you just unpacked into the profiles directory of your core installation. For example, if you have core installed at /home/www/htdocs and you downloaded and unpacked an installation profile called "fooprofile", you would want to copy the entire "fooprofile" directory into /home/www/htdocs/profiles/fooprofile. 3. Read the documentation on the installation pack you downloaded. This can either be found on the profile's project page (example: Innovation News Installation Profile) or in the README.TXT that came with the download (example: Drupal Hebrew Installation Profile). It should contain installation instructions such as listing the required extra modules and/or themes that you will have to download. 4. Download these modules and themes and put them in the right directory. This is usually sites/all/modules and sites/all/themes. 5. Go to your site's install.php and select which installation profile to install. Enjoy your fresh pre-configured Drupal installation!

After Installation Now that you've installed Drupal, there are a few tasks you need to do:

Set up cron Configuring cron is an extremely important task in your Drupal website setup. Search module's indexing of your website's content, aggregator module's retrieval of feeds, ping module's notification of other sites of updates, and system module's routine maintenance tasks, such as pruning of logs, all depend a properly configured cron job.

What is a cron job? Many Drupal modules have tasks that have to take place from time to time. Think of cron as the tolling of a bell, letting Drupal know that it should perform the appropriate tasks. The actual "cron job" is a time-triggered action that is usually (and most efficiently) performed by your website's hosting server, but can also be configured by a remote service or even from your own desktop. For your Drupal site, what actually happens is that the cron job triggers an invisible visit to the site's cron.php file (http://www.example.com/cron.php) which, in turn, executes tasks on behalf of installed modules.

Configuring cron jobs Cron is a daemon that executes commands at specified intervals. These commands are called "cron jobs." Cron is available on Unix, Linux and Mac servers. Windows servers use a Scheduled Task to execute commands. There is a video, How To: Setting up Drupal's Cron that talks about cron and shows various ways of configuring it.

The cron command In the following example, the crontab command shown below will activate the cron tasks automatically on the hour: 0 * * * * wget -O - -q -t 1 http://www.example.com/cron.php

In the above sample, the 0 * * * * represents when the task should happen. The first figure represents minutes – in this case, on the "zero" minute, or top of the hour. (If the number were, say, 10, then the action would take place at 10 minutes past the hour.) The other figures represent, respectively, hour, day, month and day of the week. A * is a wildcard, meaning "every time." The rest of the line basically tells the server to "ping" the url http://www.example.com/cron.php. Here is a diagram of the general crontab syntax, for illustration: # +---------------- minute (0 - 59) # | +------------- hour (0 - 23) # | | +---------- day of month (1 - 31)


# | | +---------- day of month (1 - 31) # | | | +------- month (1 - 12) # | | | | +---- day of week (0 - 7) (Sunday=0 or 7) #||||| * * * * * command to be executed Thus, the cron command example above means "ping http://www.example.com/cron.php at the zero minute on every hour of every day of every month of every day of the week."

How Drupal uses cron Every Drupal install requires regular actions to handle maintenance tasks such as cleaning up log files and checking for updates. Cron.php is the file that Drupal uses to run the maintenance process. For instance, if your site were www.example.com, loading the URL http://www.example.com/cron.php in your browser would run the maintenance. This page is automatically set up when you install Drupal. Simply loading the URL will run the maintenance. Nothing more is required. For a modest personal site, you might set up this cron job to run once a day. For a more active site you might want to run that job more often—perhaps every few hours or every hour. This regular visit will tell Drupal to perform its periodic tasks, and this will help keep your system running smoothly.

How to set up a cron job Cron jobs are scheduled by setting up a "crontab." A crontab is a text file that contains the commands to be run. This file can be created and edited either through the command line interface, or, if you manage your website through a web-based control panel such as cpanel or Plesk, you will use the web interface. Check with your hosting company for detailed instructions if you are using a web-based control panel. To edit a crontab through the command line, type: crontab -e

If this fails, see the Troubleshooting Cron section below. Add ONE of the following lines: 45 * * * *

/usr/bin/lynx -source http://example.com/cron.php

45 * * * * /usr/bin/wget -O - -q -t 1 http://www.example.com/cron.php 45 * * * * curl --silent --compressed http://example.com/cron.php

This would have a lynx, wget, or curl visit your cron page 45 minutes after every hour. Three options are provided in case either wget, lynx or curl are not installed on the server. Any will do the job well. Learn more about the crontab file syntax here to set up the cron job to run more or less often. There are many ways to configure a cron job. If you have full access to crontab features, you should be able to simply paste in one of the above example commands – be sure to replace "example.com" with your own web domain or docroot. If you're on shared hosting, you should be able to find cron job configuration somewhere in your hosting control panel. Some hosts even have cron "wizards" that walk you through the cron configuration, making it much easier if cron is new to you. On a Windows system you can accomplish the same thing with scheduled tasks to launch Internet Explorer pointed to the URL. Some hosting companies do not permit local loopback, so using wget, curl or lynx will not work. If this is the case, and they run PHP as a CGI (check with your hosting company to see if this is the case), the following will run the cron locally:/usr/bin/php /home/sites/example.com/public_html/cron.php

Some hosting companies don’t allow access to cron If your hosting company restricts access to cron you have many options. Ask the company to give you access, or to set up a cron job for you Ask someone else with access to a server to set up a cron job for you. Any Unix, Linux, or Mac server with access to the internet can have a cron job to regularly visit your site. There are also some companies that offer cron services. Use the Poor Man's Cron module. Find an online cron service. Many are free but with restrictions. Cron doesn't guarantee your commands will run at the specified interval. But Drupal will try its best to come as close as possible. The more you visit cron.php, the more accurate cron will be.

Troubleshooting cron jobs


If you receive a permission denied error after starting crontab -e, you may need to use sudo: sudo crontab -e

You may need to adjust the path to wget, lynx or curl in your crontab. For example, the cron example listed above contains the line: 45 * * * *

/usr/bin/lynx -source http://example.com/cron.php

However, Lynx may be in a different location on your server, or not installed at all. To find out where Lynx is installed, enter: whereis lynx

or which lynx

If it is not located at /usr/bin/lynx adjust the path as needed. The same applies for wget and curl. If none are installed ask a server administrator for help. It may be necessary to change http://example.com/cron.php to the location of your Drupal installation. For example, if you have Drupal installed in a subdirectory, it might be http://www.example.com/drupal/cron.php).

Example scripts Drupal ships with two example scripts in the scripts directory, cron-curl.sh and cron-lynx.sh. You can call these scripts from cron as well: 45 * * * * /home/www/drupal/scripts/cron-lynx.sh

Note that the scripts will need to be updated with the path to your directory and URL.

Running cron as an authenticated user #!/bin/sh # Reference http://drupal.org/node/479948#comment-1673488 by pearlbear SITE=https://dev.example.com/ USERNAME=user.name PASS=ChangeMe!!12 COOKIES=/tmp/cron-cookies.txt WGETPARAMS="--quiet -O /dev/null --no-check-certificate --save-cookies $COOKIES --keep-session-cookies --load-cookies $COOKIES" # if you run drupal in a default language different than English you need to modify this LOGIN="Log%20in" wget $WGETPARAMS "${SITE}user" wget $WGETPARAMS --postdata="name=$USERNAME&pass=$PASS&op=$LOGIN&form_id=user_login" "${SITE}user" wget $WGETPARAMS " ${SITE}cron.php"

Security notes It is possible to run cron.php directly via scripts/drupal.sh with Drupal 6. Drupal.sh allows a Drupal page to be executed from a shell script. To do so, add the following cron job to run as the Apache user. /full/path/to/drupal.sh --root /full/path/to/site/root/ http://default/cron.php

Note that http://default/cron.php is NOT shown as an example, it should be used as is, without changes. Taking this approach allows cron.php to be blocked to prevent remote access. To block remote access to cron.php, in the server, .htaccess or vhost configuration file add this section: <Files "cron.php"> Order Deny,Allow Deny from all Allow from localhost Allow from 127.0.0.1 Allow from xx.xx.xx.xx <-- your IP address </Files>

If you take this approach and use drupal.sh to call cron.php, it is probably best not to use the root user to run the cron job. A non-privileged user account, or the Apache account user, for example http-service or www-data, is a better choice. To do so, call crontab -e when logged in as a nonprivileged user, or for the Apache account on a Debian server, for example, you can add a user parameter: sudo crontab -e -u www-data

The downside to this method is that any URLs generated by cron jobs using this method will not be properly formed, starting with "http://default/".


Tips and tricks Cron Sandbox is a useful site for testing cron entries. Multiple sites If you run many sites, you can use this tip to make managing your cron jobs easier. To minimize the clutter, create a /etc/cron.5min directory and have crontab read this directory every five minutes. */5 * * * * root run-parts /etc/cron.5min

Then place multiple individual files into the /etc/cron.5min directory, one for each site. The files can be named "site1", "site2", etc. -- note that run-parts may fail to detect files which contain a dot (.) in their name. To make sure that all of your files are visible to cron, type this at a shell prompt: $ sudo run-parts --test /etc/cron.5min

and make sure that all of your files are listed. Each of the files in /etc/cron.5min should contain one line: /usr/bin/lynx -source http://(full site URL)/cron.php > /dev/null 2>&1

or, alternatively, one of the curl or wget commands specified above. If this doesn't work, try putting another line at the start of each file: #!/bin/sh

and make sure that the files are executable by doing $ sudo chmod u+x /etc/cron.5min/*

For more information about using cron in a multisite configuration, see the Multisite Cron section of this guide. SSL When using SSL, add one additional argument when calling wget: --no-check-certificate. Do not put “--no-check-certificate� between the -0 and the -. 45 * * * * /usr/bin/wget --no-check-certificate --quiet -O https://example.com/cron.php

Configuring an editor for cron You can specify which text editor (emacs, vi, nano, etc.) you want to use to edit the crontab. To tell the system which editor you want to use, type: export EDITOR=nano

Configuring cron jobs in Cpanel Cpanel controls domains. WHM, Web Host Manager, is the Cpanel product for controlling domains in a Virtual Private Server. If there is no cron option in your Cpanel, you may have to add cron in WHM. Cron may be in different places in the various Cpanel themes. In the Crimson smoke theme, you select Advanced then Cron jobs. You are offered the option of standard or an "advanced" Unix style user interface. You may see an error message similar to the following. /usr/bin/crontab permissions are wrong. Please set to 4755

The Unix style interface dumps you in a page where you can enter a command the same as described on the main Configuring cron jobs page. The standard page lets you select the number of minutes, hours, days, or months from selection lists.

Configuring cron jobs on MAMP localhost These are instructions for setting up a cron job on your localhost using MAMP on a Mac. 1. In Terminal type crontab -e. This allows you to add a cron job, using the vim editor. 2. Press i on your keyboard to go into vim's insert mode. 3. Type in you cron command. For example, to run at minute 5 of every hour using curl, use: 5 * * * * /usr/bin/curl --silent --compressed http://localhost:8888/cron.php

But to run every 5 minutes, use: */5 * * * * /usr/bin/curl --silent --compressed http://localhost:8888/cron.php

Note that if you are not using MAMP's default 8888 port, you should leave that off. 4. While in insert mode you can use the arrows and delete keys as you would normally. 5. Press escape key to exit vim's insert mode.


6. Type ZZ (Must be capital letters -- saves the file and exits crontab). 7. Verify the cron job details by typing crontab -l (that's a lower case L) at the terminal prompt. I recommend that you first try setting the job to run every 2 minutes (with */2), so that you can check, reasonably quickly, that it is running. Then edit crontab again and change the curl command to your preferred time.

Non-Vim althernative For those with a reservation against Vim (or any other command-line editor), you can do it like this: - Create a document with your editor of choice. - Add the cronjobs you want to add, like described above. - Save the file as "mycron.txt" on your desktop (or any other place you like). - Open up a Terminal window, and go to the desktop ( cd ~/Desktop ). - Type in crontab mycron.txt - Type in crontab -l to verify that your commands were added to cron. Note: This method will overwrite the entire crontab, so keep the mycron.txt file available. When you need to add or change cronjobs, just edit that file and repeat the crontab mycron.txt command to update your cronjobs.

Configuring cron jobs on DreamHost On DreamHost you can configure cron jobs using either the web panel or the shell.

Setting up cron jobs using the web panel Visit https://panel.dreamhost.com/index.cgi?tree=goodies.cron& Click 'Add a New Cron Job' and enter the following command: curl --silent --compressed http://example.com/cron.php

Note: The above code is dependent on http being accessible. If you want to run a cron job on a password protect domain, use the include the following with the absolute path for your domain to cron.php instead of the example: /dh/cgi-system/php5.cgi /home/username/example.com/drupal/cron.php

In the same interface, you should specify a title (such as 'Drupal Cron'), e-mail address for results (not recommended), and a run interval. If you have more than one Drupal site, you can add more than one command per cron job (one per line).

Setting up cron jobs using the shell This information comes from the Dreamhost knowledge base. Via the "crontab" command from the shell. crontab -l will show you your currently set up cron jobs on the server. crontab -r will delete your current cron jobs. crontab -e will allow you to add or edit your current cron jobs by using your default text editor to edit your "crontab file". Your crontab includes all the cron jobs you'd like, with one cron entry per line. A cron entry looks like this: 45 2 * * * curl --silent --compressed http://example.com/cron.php The first number is the minute of the hour for the command to run on. The second number is the hour of the day for the command to run on. The third number is the day of the month for the command to run on. The fourth number is the month of the year for the command to run on. The fifth number is the day of the week for the command to run on. Here are some examples to help you learn the syntax for the numbers: 32 * * * * : will be run every hour on the 32nd minute. 12,42 * * * * : will be run twice an hour on the 12th and 42nd minutes. */15 */2 * * *: will be run at 0:00, 0:15, 0:30, 0:45, 2:00, 2:15, 2:30, ... 43 18 * * 7: will be run at 6:43pm every Sunday. You can also edit crontab on a local file. Upload it to a directory and run: crontab YourFileName It'll replace your crontab with what's on the text file, without having to edit it online. This is useful if you're using a telnet client which doesn't support ANSI control codes (such as the Windows' one) and can't understand what the heck is going on on the editors. In my own experience, I found that only by entering one cron command per line worked (one line each for :10, :20, :30, etc.). I could not get crontab to take the "*/15" argument, so I worked around it. Also, if you have ANY character after the last command (space, CR, whatever) the file


will not take. I like this file-load approach because I have a basic command file to edit or call up again if needed. Afterwards, running crontab -l will confirm that the job did take.

Configuring cron jobs on Media Temple Grid Servers (gs) Through my experience and to my knowledge, cron jobs on Media Temple's Grid Servers must be run via an .sh file in the "/scripts" directory of your Drupal installation. This was confusing to me as I've always setup cron jobs in cPanel. Media Temple does not use cPanel. Following the steps to setup a cron job like 45 * * * * /usr/bin/wget -O - -q http://www.example.com/cron.php through a typical cPanel interface will not work in Media Temple's cron job creation tool. Use the following steps to get cron jobs working on a Media Temple Grid Server: 1. 2. 3. 4.

Log into your (mt) AccountCenter and go to your WebControl panel. Click on the "Cron Jobs" icon to enter the cron configuration page. Select the "Add a new cron job" button. Enter an email address for the "Output email". Note:Leave this blank if you don't want constant emails. 5. Enter the following in the "command to run" text box: curl http://www.example.com/cron.php

where "example.com" is your domain name. 6. Under "Scheduling Settings" select how often you want your cron job to run. 7. Note the Media Temple will not allow a cron job to run more often than "every 5 minutes". To setup a cron job to run once an hour: Under the "minutes" entry, specify the minute value on which the cron job will start each hour. Then select the following radio boxes: hour, day, month, day of the week.

Configuring cron jobs on Windows To setup a Windows machine to run cron.php at a specific time follow the specific instructions below. This can be useful if you are not familiar with Linux/Unix, or if your web host does not offer the ability to run cron jobs; you can run them remotely from your own computer. Note: These instructions were written for Windows XP but should be similar in other versions of Windows. Creating a Scheduled Task 1. 2. 3. 4. 5. 6. 7. 8. 9.

Open Scheduler Go to Start > Programs > Accessories > System Tools > Scheduled Tasks Double-click Add Scheduled Task The Scheduled Task Wizard will appear. Click Next. Select the program to run. Choose your browser from the list (for example, Internet Explorer or Mozilla Firefox). Click Next. Give the task a Name, such as Drupal Cron Job, and choose the Frequency with which to perform the task (for example, Daily)). Click Next. Choose specific date and time options (this step will vary, depending on the option selected in the previous step). When finished, click Next. Enter your password if prompted. Change the username if required (for example, you'd like the task to run under a user with fewer privileges security reasons). Click Next. On the final page, select the checkbox Open advanced properties for this task when I click Finish and click Finish.

Configuring the task 1. Go to the task's setting page either by checking the checkbox at the end of the last step, or by double-clicking on the task. 2. In the Run box, after the text that is there now (for example, C:\PROGRA~1\MOZILL~1\firefox.exe), enter a space and then type the address to your website's cron.php page in double quotations (for example, C:\PROGRA~1\MOZILL~1\firefox.exe http://www.example.com/cron.php 3. To set a frequency more often than Daily (for example, hourly), click the Schedule tab, then click Advanced. Here you can set options such as Repeat task, every 1 hour for 23 hours. Click Ok when finished. 4. Change the start time on the task to one minute from the current time. This will allow you to test the task and make sure that it is working. 5. When all settings have been configured to your liking, click Apply and OK (note: you may be prompted for your password) Command-line version Another way to perform the above commands is by using the schtasks (or at in Windows 2000) command from the command line. To duplicate the example above, which runs Firefox hourly to execute http://www.example.com/cron.php, open a command prompt (Start > Programs > Accessories > Command Prompt) and enter:


schtasks /create /tn "Drupal Cron Job" /tr "C:\PROGRA~1\MOZILL~1\firefox.exe http://www.example.com/cron.php" /sc hourly

Enter your password if prompted. Using wget for Windows to run cron If for whatever reason you'd rather not deal with a browser window popping up on the machine, you can use wget, the Windows port of which works more or less the same as it does in UNIX. curl and lynx also have windows ports but wget is probably the easiest to set up and use. Grab a copy of wget from your choice of either the author's site or from SourceForge. Install it to the location of your choice. Follow the steps for Creating A Scheduled Task above, except select wget.exe as the program to run (you may need to click the Browse button to locate it if you installed from a .zip file, for example). When you get to the Advanced Properties dialog, paste in the following after the program path: -O - -q -t 1 http://www.example.com/cron.php

Adjust the rest of the options as described above and test it.

Configuring cron jobs on ixWebHosting Login to the control panel at https://manage.ixwebhosting.com/index.php Click on "Manage" following this path: My Products >> Hosting Products >> Manage This will take you to H-Sphere page. Click on "FTP Manager" under FTP. You'll see one of the parameters as Crontab. The Value will be set to Off by default. Click on the Off button.(Note: If it's been set to On, click on Edit button) This will take you to list of cron jobs(should be empty at first). Fill in the Mail-To field with an email address where you want cron run success/failure messages sent. The other fields are: TT- Opens up a help popup. Minute - Set to "0" to run at every time the clock strikes 0th minute. Hour - Set to "*" to run every hour or say for example "8" to run at 8am in the morning. Day of month - Set to "*" to run every day in the month. Month - Set to "*" to run every month. Day of week - Set to "*" to run every day of week. Command - This is the only tricky part. Copy paste command below replacing "example.com" with the actual site name. /usr/bin/curl -o /dev/null http://example.com/cron.php Delete - Can be checked to delete previously stored cron jobs.(will otherwise state "New") That should be it. Click on "Submit Query" to save cron job. You can add multiple ones by filling in the last row which is always empty and cannot be deleted. Hope this helps. If pictures help, I'll re-edit this post with some.

Configuring cron on Mac OS X Server 10.5.x and later Drupal documentation refers to running cron jobs periodically. But OS X Server (at least Leopard Server) has a kernel bug that results in a log message from cron jobs to the effect “Could not setup Mach task special port 9: (os/kern) no access”. Not a joyful message to come across, though it appears to be harmless. In addition, OS X doesn’t come with wget, so the suggested cron call in the Drupal docs doesn’t work anyhow. To avoid both of these problems, put a call to curl in a launchd daemon .plist file (e.g. /Library/LaunchDaemons/drupal-cron-call.plist), thusly: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.{domain-name}.drupal-cron</string> <key>UserName</key> <string>nobody</string> <key>GroupName</key> <string>_www</string> <key>ProgramArguments</key> <array> <string>/usr/bin/curl</string>


<string>-s</string> <string>http://localhost/{drupal-subdirectory/}cron.php</string> </array> <key>RunAtLoad</key> <true/> <key>StartCalendarInterval</key> <dict> <key>Minute</key> <integer>09</integer> </dict> </dict> </plist>

Then launchctl load /Library/LaunchDaemons/drupal-cron-call.plist and enjoy. Notes: “http://localhost/{drupal-subdirectory/}cron.php” might be replaced with “http://{domainname}/cron.php” for a production server. Be sure to replace {domain-name} and/or {drupal-subdirectory/} placeholders (in sample .plist file above) with appropriate values. For non-production environments, StartCalendarInterval, which always runs at nine minutes past the hour in this example, should perhaps be replaced with StartInterval, which always runs after a specified number of seconds have elapsed (since the last run). For production environments, it’s nice to run cron by the clock, but development computers may be asleep during arbitrary times and thus benefit from the elapsed-time approach. To change this, replace: <key>StartCalendarInterval</key> <dict> <key>Minute</key> <integer>09</integer> </dict>

with: <key>StartInterval</key> <integer>3600</integer>

(This example runs every hour.)

Configuring cron on Rackspace Cloud Sites In the Features tab of your domain, go to "Scheduled Tasks (Cron Jobs)". Then: 1. Click on "add new task" 2. Enter a task name (such as "Drupal Cron") 3. Enter an email address for output (results of each cron run will be emailed to this address) 4. For Command Language, select "HTTP" 5. For Command to Run, enter the url of the cron.php file (for example, http://www.example.com/cron.php) 6. Select the interval to run the cron task (no more frequently than once every 5 minutes) 7. Click on "Save Task" That should do it.

Cronjobs without wget/lynx or curl Some of you might have trouble getting lynx or wget to work. This may be the case if "localhost" is not permitted. Then wget, lynx or curl won't work on the local machine. If you are not willing to depend on others to perform cronjobs and you dislike poormans cron, try this script. touch ~/scripts/cron-php.sh chmod 700 ~/scripts/cron-php.sh vi ~/scripts/cron-php.sh #!/bin/bash # # Simple cron script for those who cannot # use wget, lynx or curl because the host # closed the localhost loop. # # Usage: # - put this file in the ~/scripts dir # - add a cronjob pointing to this script # # Copyright OCS - 2006 # Copyright hansfn@drupal.org - 2008 # This script is provided under the GPL. # # v 0.1 original release # V 0.2 added PHP parser options and verbosity # setting to keep it quiet so real errors can # be reported by cron. ############################# # CONFIGURATION OPTIONS ############################# # Complete local path #


# Set the complete local path # to where the cron.php file # is (ie the root path) # Default is /var/www/html/ root_path=/var/www/html/ # Complete php path # # Set the complete path # to the php parser if # different from standard parse=/usr/bin/php # PHP parser options # # Defaulting to not report notices. parse_options="-d error_reporting=2037" # Verbosity # # Default to only report errors. verbose=false ############################## # END OF CONFIGURATION OPTIONS ############################## cd $root_path if [ -e "cron.php" ] then $parse $parse_options cron.php if [ "$?" -ne "0" ] then echo "cron.php not parsed." else if $verbose then echo "cron.php has succesfully been parsed." fi fi else echo "cron.php not found." exit fi exit

and then the crontab itself: crontab -e * * * * * ~/scripts/cron-php.sh where ~ is the directory where your drupal install is (the script defaults to /var/www/html/ but your professional hosting company will probably use /home/your.domain.tld/www

Another multi site cron without lynx/wget or curl Try A multi-site cron script without lynx/wget/curl

Multisite cron without wget/lynx or curl I recently moved my sites to a new host, and they don't allow me to use either wget or curl from cron. Since I'm running multiple sites, I also can't just run cron.php directly. However, I can use the 'exec()' PHP call, so I came up with these two scripts: runcron.php: #!/usr/local/bin/php <?php $SITES = array( 'www.mainsite.com', 'www.subsite1.com', 'www.subsite2.com', ... ); $_SERVER['SCRIPT_NAME'] = '/cron.php'; $_SERVER['SCRIPT_FILENAME'] = '/cron.php'; foreach( $SITES as $site ) { exec( "./runsitecron.php ".$site ); } ?>

runsitecron.php: #!/usr/local/bin/php <?php $_SERVER['SCRIPT_NAME'] = '/cron.php'; $_SERVER['SCRIPT_FILENAME'] = '/cron.php'; $_SERVER['HTTP_HOST'] = $_SERVER["argv"][1]; include "cron.php"; ?>


Then I just call 'runcron.php' from my crontab and it runs Drupal's 'cron.php' for each of my sites.

Using Perl to run cron If you're having problems with cron using curl and wget is not allowed on your server try out the curl call from the shell on another box - you may find you get HTML delivered saying "page not found." If this is the case you can also use Perl to perform the call to the cron script. You need command line access to Perl and the LWP::Simple module installed. Your crontab entry then becomes: 45 * * * * perl -MLWP::Simple -e "getprint 'http://yourdomainhere/cron.php'"

Multisite cron Sites that make use of Drupal's multisite feature need to take extra steps to ensure that each site gets it cron run, rather than just the default site. The following pages have ways that people have addressed this issue. Feel free to add your own solution as a child page.

Automatic multisite cron using php5 Create a new file preferably in the same directory as your drupal dir. Name it 'cronall.php' or something. Copy the following script into it. Then adjust the settings in the file, and visit the script with your webbrowser, to see if it is setup correctly. Then create a cronjob for the cronall.php script, and you're done. <?php /** * This script scans the sites directory, and uses a regular expression to extract the sitenames. * It then uses this sitename to execute the cronjob for these sites. * You then only have to create a cronjob for this script. * In this way, you can create and delete sites on the fly, but all their cronjobs will be executed. */ /*********** * SETTINGS **********/ //the location of the 'sites' directory relative to this script. $sitesDir = '../drupal/sites'; /** * A regular expression that matches the name of the directories in * the 'sites' dir that you want to execute cronjobs for, with a * backreference around the actual site name. (so we can exclude the * domain part) * * If you don't know regular expressions you might want to brush up * on them: <a href="http://www.regular-expressions.info/tutorial.html " title="http://www.regular-expressions.info/tutorial.html " rel="nofollow">http://www.regular-expressions.info/tutorial.html </a> * * Alternatively, you can just copy the name of one of the directories * in the site dir, put a backslash \ in front of all dots . and replace * the actual name of the site with ([a-zA-Z0-9_-]) */ $siteNameRegExp = 'www\.example\.com\.([a-zA-Z0-9_-])'; //the url of the cron script, you should insert %s where the sitename should be inserted later. $cronUrl = 'http://www.example.com/%s/cron.php'; //any other sites that you want to execute cronjobs for. Just comment this if you haven't got any. $addedSites = array('drupal'); /*********** * END SETTINGS **********/ error_reporting(E_ALL); $sites = array(); $handle = opendir($sitesDir); while ($file = readdir($handle)) { if(ereg($siteNameRegExp, $file)){ $sites[] = ereg_replace($siteNameRegExp, '\\1', $file); } } //default site if(isset($addedSites) && is_array($addedSites)){ $sites = array_merge($sites, $addedSites); } foreach($sites as $site){ $cmd = 'wget --spider '.sprintf($cronUrl, $site); echo 'Executing command: '.$cmd.'<br>'; exec($cmd); } ?>


Cron script for multi source, multi site setup I've just hacked up this little script as I have multiple installations of drupal each with multiple sites that get more each day. This should figure out which sites cron should run for. #!/bin/bash SITESROOT=/var/www/sites MYIPRANGE=41.204.221 # get the base installs cd $SITESROOT for drupaldir in $(find . -maxdepth 2 -name INSTALL.mysql.txt | awk -F/ '{print $2}') do cd $SITESROOT/$drupaldir/sites for site in $(find -L . -maxdepth 1 -type d -iregex "./[a-z].*\.[a-z].*" | awk -F/ '{print $2}') do IP=$(dig $site | sed '/AUTHORITY SECTION/,$d' | grep -v "^;" | grep "IN[[:space:]]*A" | sed 's/.*A\W*//' ) if echo $IP | grep -q $MYIPRANGE then #echo "Doing cron for $site" wget -O - -q http://$site/cron.php else a=1 #echo "Skipping cron for $site" fi done done

Multi-site Cron Bash Script A quick bash script to run cron for multiple sites: #!/bin/bash ## Define your websites in an array sites=(example.com example2.com example3.com) len=${#sites[*]} for((i=0; i<$len; i++)); do wget -q http://${sites[${i}]}/cron.php # You may remove (or comment) the following line if you # wish to retain the downloaded file. rm cron.php done

Multisite cron without wget/curl #!/usr/bin/php5 <?php /** * This script scans the sites directory, and uses a regular expression to extract the sitenames. * It then uses this sitename to execute the cronjob for these sites. * You then only have to create one cronjob for this script. * In this way, you can create and delete sites on the fly, but all their cronjobs will be executed. */ /* * Carl van Denzen, june 2009: * Options for this script: * -h <hostname> (p.e. arjan.vandenzen.nl) * -r <regexp> (p.e. .+\.vandenzen\.nl) This will be used in most cases. * -a * * When this script is invoked with parameter -r, then it will call * itself for every host in the sites directory that matches the -r regexp. * These calls will be with the -h <hostname> argument set to the site name. * * When invoked with -h option (only ONE is allowed), it will run cron * for the named hostname site. * * When this script is invoked without parameters, it will die. This is to avoid * runaway scripts. * * Purpose of this behaviour: * * You can add this script to your crontab with parameter -a: it will run * for every site found in the drupal sites directory. This is the regular * drupal/cron.php behaviour. * In a one-site set-up it will only run for the default directory. * In a multi-site set-up it will run for all sites (beware of the default directory?) * * You can add this script to your crontab with the -r parameter and it will only run cron * for the sites that match the <regexp>. This is primarily meant for a multisite set-up * when the sites directory contains sites that you want to exclude from running


cron * (p.e. the "default" site). * * You can add this script to your crontab with the -h option to run cron * for only the specified site (only one allowed). * * Without parameters it will do nothing. * I have seen some problems with argument parsing that made me * afraid of doing things like calling the same script. I would have preferred * that this script would cron all sites if it was invoked without parameters. * * This script calls itself for every site (in a new command shell). * It is impossible to do this in a * php function (i.e. without invoking a new process) because the drupal * bootstrap coding cannot (easily) be called multiple times. * * Disadvantages of this script: * pearl5 is needed for Console/Getopt.php * It is not efficient because for every host a new php process is * created. * * Advantage: * For me it works. * It is only one script (I saw other solutions that used two scripts * to accomplish this task). * It doesn't use wget or similar programs that try to start cron * by making a connection to the cron.php file. This strategy didn't * work for me, because my website hoster doesn't allow outgoing * connections. */ /*********** * SETTINGS **********/ //the location of the 'sites' directory relative to $sitesDir = 'sites'; /** * A regular expression that matches the name of the * the 'sites' dir that you want to execute cronjobs * backreference around the actual site name. (so we * domain part) * */ $siteNameRegExp = '(.*\.example\.com)'; $debug=0; /*********** * END SETTINGS **********/

this script. directories in for, with a can exclude the

/* * Do default action like old cron.php (i.e. before * the year 2008) script in Drupal 6.x */ function do_old_cron() { drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); drupal_cron_run(); } /* * some sanity checks */ if (!isset($_SERVER['argv'])) { // print('server argv not set<br/>'); do_old_cron(); } error_reporting(E_ALL); include ("Console/Getopt.php"); // initialize object $cg = new Console_Getopt(); /* define list of allowed options - p = a:all sites, h:one site, r:sites that match regular expression */ $allowedShortOptions = "ah:r:"; // read the command line $args = $cg->readPHPArgv(); // get the options $ret = $cg->getopt($args, $allowedShortOptions); // check for errors and die with an error message if there was a problem if (PEAR::isError($ret)) { die ("Error in command line: " . $ret->getMessage() . "\n"); } ini_set('include_path',ini_get('include_path'). PATH_SEPARATOR . './scripts'); /* This doesn't work in every case: getopt function is not always available $options = getopt("h:r:"); var_dump($options); */ include_once './includes/bootstrap.inc'; function cron_one_site($sitename) { $_SERVER['SCRIPT_NAME'] = '/cron.php'; $_SERVER['SCRIPT_FILENAME'] = '/cron.php';


$_SERVER['HTTP_HOST'] = $sitename; $_SERVER['REMOTE_ADDR'] = 'localhost'; $_SERVER['REQUEST_METHOD'] = 'GET'; drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); echo "Hostname is: $hostname<br/>\n"; print 'conf_path is '.conf_path() . '<br/>'; drupal_cron_run(); } /* * Call this script for every site in regexp (call * it with -h option for every site). */ function cron_regexp($siteNameRegExp) { global $sitesDir; global $debug; $sites = array(); // Get the name of this script, so we can call it recursively // doesn't work: argv is not defined: $thisScript=$argv[0]; $argv=$_SERVER["argv"]; $thisScript=$argv[0]; $handle = opendir($sitesDir); while ($file = readdir($handle)) { if ($debug>0) { if (file_exists("$sitesDir/$file/settings.php")) { print('Yes: '); } else { print("No: "); } print("exists $sitesDir/$file/settings.php<br/>"); } if (($file!='all') && (file_exists("$sitesDir/$file/settings.php"))) { // preg expects the pattern to be enclosed in a (freely chosen) // delimiter. I have chosen ^ because I think that will never // be used in a host name if(preg_match('^'.$siteNameRegExp.'^', $file)){ if ($debug>0) { print '$file is '.$file.', $siteNameRegExp is '.$siteNameRegExp.'<br/>\n'; } $sites[] = $file; } } } foreach($sites as $site){ if ($debug>0) { print 'Doing site '.$site.'<br/>\n'; } $commandline='/usr/bin/php5 '.$thisScript.' -h '.$site; exec($commandline,$out1,$out2); if ($debug>0) { print 'Commandline is '.$commandline.'<br/>\n'; print 'out1 is '.implode('\n',$out1).'<br/>\n'; print 'out2 is '.implode('\n',$out2).'<br/>\n'; } } } // display the options //print_r($ret); if ($debug>0) { print_r($_SERVER); } // parse the options array $opts = $ret[0]; if (sizeof($opts) > 0) { // if at least one option is present foreach ($opts as $opt) { switch ($opt[0]) { // handle the all sites option case 'a': $re='.*'; cron_regexp($re); break; // handle the hostname case 'h': $hostname = $opt[1]; cron_one_site($hostname); break; /* handle the regexp option. */ case 'r': $re = $opt[1]; // regular expression cron_regexp($re); break; default: print 'Usage: <br/>\n'; print '- h hostname<br/>\n'; print '- r regexp<br/>\n'; print '- a (do all sites)<br/>\n'; break; } } } /* * Some experienced I had with this script at your-webhost.nl in june 2009 * // $_SERVER["PHP_SELF"] is not set */


?>

Multisite with cron-curl.sh I have domain1.com hosted as the primary domain with domain2.com "parked" at domain1.com. My public_html/sites folder looks like this: sites/all sites/default sites/domain2 My webhost (an Apache server running Cpanel 11) blocks user access to lynx or wget, so the normal recommendations for setting up a crontab were not working for me. However, I discovered a suggestion in http://drupal.org/cron to try the cron-curl.sh script that ships with Drupal in the "scripts" folder. I edited cron-curl.sh so that it includes the following line: curl --silent --compressed http://domain1.com/cron.php

And then I set up a crontab that looks like this: */2 * * * * /home/webhostaccountname/public_html/scripts/cron-curl.sh

After remembering to set the file permissions on cron-curl.sh so that it could be executed, the cron job worked perfectly. The "*/2" executes the crontab every other minute for testing purposes only. So, then I went back into cron-curl.sh and added the following line: curl --silent --compressed http://domain2.com/cron.php

Again, success! I'm watching the logs for both domain1.com and domain2.com. Cron jobs are being executed every 2 minutes for both domains.

Run cron.php with python. I was unable to run cron.php from the cpanel cron on a multisite because I had no access to curl, lynx and wget. I knew I had python installed in the shared server so what I did was to create a python script in my home folder called wget.py import sys, urllib def reporthook(*a): print a for url in sys.argv[1:]: i = url.rfind('/') file = url[i+1:] print url, "->", file urllib.urlopen(url)

Then, in the cron configuration, just add the command: /path/to/python /path/to/wget.py url1/cron.php url2/cron.php url3/cron.php

Attachment Size wget.py_.txt 218 bytes

Simple wget multisite cron, lazy method I just wrote this script for running the crons on all my drupal sites before I discovered this section in the documentation. This works well for me because I'm lazy and I don't have to maintain a list of my drupal sites somewhere for it to run all my crons. The script simply reads the domains from my sites folder. It works well for me, but I think it might not work for every situation, ymmv. Create an executable called drupalcron: #!/bin/bash #change the following line to match your sites folder cd /var/www/drupal/htdocs/sites #the next line gets only directories, not symlinks (and not the 'all' nor 'default' directories) for f in `ls -Fd *.*|grep '/$'` #remove the 'echo' from the following line after making sure it looks right do echo wget -O - -q -t 1 http://${f}cron.php done

Please note, the script will only echo the command and not actually do anything. This is so that you can check to see if it looks right first. Please read the comments in the script. If the output is what you expect, then delete the word echo from the do line and then it will execute the wget commands next time. Then simply add that to your crontab however you wish: 10 * * * * /path/to/drupalcron > /dev/null 2>&1


Running cron manually To run a cron job manually, simply visit your own site's url at http://example.com/cron.php with your web browser, and cron will run and complete in the background. You'll be left with a blank white page, which is normal. Use the browser's back button to return to the previous page and continue where you left off. It doesn't matter how long you wait while you're seeing the blank white page, and returning to the previous page at any time won't stop the cron process from running. Core provides a convenience link for running cron manually too. Look at the Status page's link: /admin/logs/status/run-cron?destination=admin%2Flogs%2Fstatus. You can paste that into your own site's address bar any time you want to run a cron. In addition, if you have the update_status.module installed, and you should since it's simply glorious, you'll be presented with a custom link on your admin/logs/updates page. For Drupal 6.x, see in your site: Administer > Reports > Status report > Cron maintenance tasks > Run cron manually.

Setting up cron on Hostmonster through the cPanel interface 1. Logging into the cPanel interface for your hostmonster account, scroll to the bottom and look in the "Advanced" section 2. Select "Cron Jobs." It is represented with a calendar icon 3. Select "Standard" Do not use an email address or you will be hit with a notification every time the cron job runs 4. Enter the following command: php /home/+++Your hostmonster login username+++/public_html/cron.php This is only for your main domain, subdomains are in different folders 5. Select the number of times that you would like the cron job to run 6. See if it works -you can check whether or not a Cron Job has been run in the Logs menu in Drupal

Solving cron problems Various things can keep cron from doing its job on your Drupal site. If your Status report ( admin/reports/status) indicates that cron hasn't run recently, try the following to diagnose and fix the problem:

Make sure cron.php is being called at all If your Status report indicates that cron has never run, you may simply need to configure configure a cron job. If you can successfully run cron manually from your Status report screen, Admin menu, or Drush, this is almost certainly the case. If manually running cron fails, you have a deeper problem...

Check for problems with cron itself If cron hangs, redirects, returns a 404 error, or just plain fails when run manually, you have a problem with cron itself. Cron may be cached badly. Clear your site caches from admin/settings/performance, Admin menu, or Drush, or truncate (empty) all database tables with names beginning with cache_. You could also search for cached instances of cron. Cron may have been interrupted during execution, such as by a server restart. This can result in watchdog (error) messages like "Attempting to re-run cron while it is already running" and "Cron has been running for more than an hour and is most likely stuck". Try resetting the semaphore and deleting the "last run" timestamp from the database. (Note: deleting the "last run" timestamp will cause your Status report to indicate that cron has never run.) You can do so with this SQL... USE Name_Of_Your_Drupal_Database; DELETE FROM variable WHERE name="cron_semaphore"; DELETE FROM variable WHERE name="cron_last";

...or with this PHP in a custom module or script: <?php variable_del('cron_semaphore'); variable_del('cron_last'); ?>

Cron may be taking too long to complete, resulting in WSOD or in watchdog (error) messages like "Cron run exceeded the time limit and was aborted". This is especially common on sites that have been online for some time where database tables cron is attempting to clean up have become unmanageably large. Check the size of the watchdog, sessions, and accesslog tables and truncate (empty) them if necessary.


, and tables and truncate (empty) them if necessary. A cron process with too much to do may also easily run out of memory, resulting in PHP error messages like "PHP Fatal error: Allowed memory size of X bytes exhausted (tried to allocate Y bytes) in file on line x". Try increasing PHP's memory limit.

Check for problems with modules Certain modules may cause cron to abort, redirect, or return a 404 error. To find an offending module, first find out which ones implement hook_cron. Print a list using this code: <?php echo theme('item_list', module_implements('cron')); ?>

Disable the modules, one by one, running cron after each one. (At least) the last module you disabled before it starts working again was causing a problem. Core search module is frequently the source of problems because of errors in nodes it's trying to index. Cron may choke on following things in your content: Invalid PHP code Non-ASCII characters drupal_goto (See #102138: cron breaks on drupal_goto.) If you know cron is failing during search indexing and you suspect bad content to be to blame, you can use the following SQL to see which nodes haven't been indexed yet—chances are it's choking on one of them. SELECT * FROM node n LEFT JOIN search_dataset d ON d.type = 'node' AND d.sid = n.nid WHERE n.status = 1 AND n.type IN ('blog', 'page') AND (d.sid IS NULL OR d.reindex > 0)

The files directory In most cases, Drupal would create the files directory for you. If Drupal can't create the directory, follow the instructions below. After installing Drupal, it is helpful to have a writable directory so that you can upload your own content files. If you skip this step, you may get an error message stating that "sites/default/files does not exist ..." Here’s how: 1. Make a directory called 'files' in the sites/default folder. 2. Assign write permissions to it with the following command (from the installation directory): chmod -R a+wrx sites/default/files

or chmod -R 777 sites/default/files

Also, most FTP programs allow you to create the files directory and set its permissions. Be sure to give read, write, and execute permissions to everyone (777). FYI: chmod a+rwx is equivalent to chmod 777

Troubleshoot installation problems Links to some of the more common installation issues are listed below. More troubleshooting information is available at the Troubleshooting FAQ.

Fatal error: Allowed memory size of n bytes exhausted The error "Allowed memory size exhausted" indicates that Drupal needs more memory than has been allocated under current settings. This error can be resolved by adding an extra line to Drupal's configuration files.

Failed to connect to your MySQL database server The installation script cannot connect to the database. Check if the username, password and hostname that you provided are correct.

Cannot create directories 'files' or 'private' The installation script needs permissions to create these directories inside the sites/default folder.

Cannot write to configuration files settings.php


The installation script needs permissions to write to the configuration files settings.php.

Blank page (White Screen of Death) Occasionally the page is completely blank (No content. No errors.) This is what is sometimes referred to as the "White Screen of Death". There are several reasons why this might occur

"Your PHP configuration only supports the SQLite database type" on the database configuration page during install Drupal 7 now requires PHP to have the PDO (Php Data Object) extension enabled or compiled in. Older or home-brewed versions of PHP may not have this enabled by default. Check your phpinfo for the "pdo_mysql" extension, or see the instructions for enabling it. Discussion

Other errors Host-specific error messages Register globals should be disabled What permissions does Drupal need? Why is this uploading stuff so difficult All my pages are blank White screen of death fixing step by step Client does not support authentication protocol 500 internal server error Got a packet bigger than 'max-allowed-packet' Lock tables sequences write error Error on installation step 3 Database setup fails to proceed Translation issues Clean URLs how-to

Advanced and multisite installation The following pages cover some special installation cases, such as running several Drupal sites from a single code base (also known as multi-site installations) and specifying a database table prefix.

Modify the file system path The files directory created in the initial install is the default file system path used to store all uploaded files, as well as some temporary files created by Drupal. After installation, the settings for the file system path may be modified to store uploaded files in a different location. It is not necessary to modify this path, but you may wish to change it if: your site runs multiple Drupal installations from a single codebase (modify the file system path of each installation to a different directory so that uploads do not overlap between installations); or, your site runs a number of web server front-ends behind a load balancer or reverse proxy (modify the file system path on each server to point to a shared file repository). To modify the file system path: 1. Ensure that the new location for the path exists or create it if necessary. To create a new directory named uploads, for example, use the following command from a shell or system prompt (while in the installation directory): mkdir uploads

2. Ensure that the new location for the path is writable by the web server process. To grant write permissions for a directory named uploads, you may need to use the following command from a shell or system prompt (while in the installation directory): chmod o+w uploads

3. Access the file system path settings in Drupal by selecting these menu items from the Navigation menu:Administer > Site configuration > File system 4. Enter the path to the new location (e.g.: uploads) at the File System Path prompt. Note Changing the file system path after files have been uploaded may cause unexpected problems on an existing site. If you modify the file system path on an existing site, remember to copy all files from the original location to the new location. Some administrators suggest making the documentation files, especially CHANGELOG.txt, nonreadable so that the exact version of Drupal you are running is slightly more difficult to determine. If you wish to implement this optional security measure, use the following command from a shell or system prompt (while in the installation directory): chmod a-r CHANGELOG.txt

Note that the example only affects CHANGELOG.txt. To completely hide all documentation files from public view, repeat this command for each of the Drupal documentation files in the installation directory, substituting the name of each file for CHANGELOG.txt in the example.


Run multiple sites from the same code base (multi-site) If you are running more than one Drupal site, you can simplify management and upgrading of your sites by using the multi-site feature. Multi-site allows you to share a single Drupal installation (including core code, contributed modules, and themes) among several sites. This is particularly useful for managing the code since each upgrade only needs to be done once. Each site will have its own database and its own configuration settings, so each site will have its own content, settings, enabled modules, and enabled theme. However, the sites are sharing a code base and web document root, so there may be security concerns (see section below for more information).

Overview of the Process To create a new site using a shared Drupal code base you must complete the following steps: 1. Create a new database for the site. 2. Create a new subdirectory of the 'sites' directory with the name of your new site (see below for information on how to name the subdirectory). 3. Copy the file sites/default/default.settings.php into the subdirectory you created in the previous step. Rename the new file to settings.php. 4. Adjust the permissions of the new site directory, and grant write permissions on the configuration file 5. Make symbolic links if you are using a subdirectory such as example.com/subdir and not a subdomain such as subd.example.com (see the subdirectory multi-site section below for details). 6. In a Web browser, navigate to the URL of the new site and continue with the standard Drupal installation procedure. It may also be necessary to modify your Web server's configuration file (often named httpd.conf for Apache) to allow Drupal to override Apache's settings. This is true for all installations of Drupal and is not specific to the multi-site install. Additional information is available in the Best Practices: Configuring Apache and PHP for Drupal in a Shared Environment section of the Install Guide.

Details of the Process Domains, URLs, and sites subdirectory name The new subdirectory of the sites directory has a name that is constructed from the site's URL. For example, the configuration for www.example.com would be in sites/example.com/settings.php. You do not need to include 'www' as part of the directory name. Drupal will use the same sites/example.com directory for any subdomain of example.com, including www, unless there is an alternative, matching subdomain sites subdirectory. For instance, URL http://sub.example.com would be served from sites/sub.example.com, if it exists. For a subdirectory URL, such as http://example.com/subdir, name the sites subdirectory as follows: sites/example.com.subdir -- and read the section below on getting subdirectory multisite working. If you are installing on a non-standard port, the port number is treated as the first part of the subdomain. For example, http://www.example.com:8080 could be loaded from sites/8080.example.com. If that directory doesn't exist, Drupal would then look for sites/example.com, just like a real subdomain.

Site-specific modules and themes Each site configuration can have its own site-specific modules and themes in addition to those installed in the standard 'modules' and 'themes' directories. To use site-specific modules or themes, simply create a 'modules' or 'themes' directory within the site configuration directory. For example, if sub.example.com has a custom theme and a custom module that should not be accessible to other sites, the setup would look like this: sites/sub.example.com/settings.php sites/sub.example.com/themes/custom_theme sites/sub.example.com/modules/custom_module

Document root One area of frequent confusion is that in a Drupal multisite installation the webserver document root is the same for all sites. For example with the following three sites: example.com, sub.example.com and example.com/site3 there will be a single Drupal directory and all sites will be calling the same index.php file. Some webhosts automatically create a new directory (i.e. example.com) when creating a new domain or subdomain. In this case it is necessary to make it into a symbolic link to the main


Drupal directory, or better yet when creating the domain or subdomain, set it to use the same document root as the site where you have Drupal installed.

Subdirectory multi-site If you are attempting to get Drupal multi-site working using subdirectory URLs rather than subdomain or different domain URLs, you may encounter problems. You'll start out by making a directory such as sites/example.com.subdir, and putting a settings.php file there. If this works for you, great! But it probably will not, until you make a symbolic link that tells your web server that the document root for http://example.com/subdir is the same as the document root for http://example.com. To do this, go to the example.com document root and type: ln -s . subdir

(substituting the actual name of the subdirectory you want to make work). If your codebase itself is in a subdirectory, then link your new site to the directory: ls -s drupaldir subdir

This symbolic link is enough to resolve issues with subdirectory multi-site installations on at least some web hosts. But if you still encounter problems, such as issues getting clean URLs to work (especially if you had to edit the .htaccess file to get clean URLs working on the main example.com domain site), you may find this forum suggestion useful: http://drupal.org/node/239583#comment-786932 Note that these problems do not usually occur for multi-site installations using a subdomain, so you might want to try that if you cannot get subdirectory multi-site to work. (However, subdomains are not good for search engine rankings!)

Localhost alias for local workstation On many systems it is possible to create entries in a "hosts" file to create aliases for the localhost name for a local workstation. By creating aliases for localhost it is possible to create names such as localdev1.example.com and localdev2.example.com, both for the local computer. If on the other hand you use subdirectories in your local web root, create a symbolic link like this: ls -s drupaldir subdir

and name your site folder localhost.subdir.

Domain name changes Once a site is in production in a particular subdirectory under the sites directory, the subdirectory should not be renamed, even if the Web site URL changes. This is because several database tables (for example: system and files) include references to "sites/www.mydomain.com." Instead of renaming the sites directory, you can create a symlink to the new URL from the old one. Navigate to the sites directory and then use the following command: $ ln -s /path/to/drupal/sites/old.domainname.com new.domainname.com

Security Concerns You might want to reconsider using Drupal's multi-site configuration, in situations where the administrators for all of the sites being run from the same code base are not either the same person or a small group with a high level of mutual trust. The reason is that anyone with full administrative privileges on a Drupal site can execute arbitrary PHP code on that site through various means (even without FTP access to the site), and that arbitrary PHP code could be used from one site to affect another site, if the two sites are in the same HTTP document root and sharing the same Drupal code. So, unless you completely trust all of the administrators of the sites you are considering running from the same Drupal code base to be knowledgeable, careful, and not malicious, you might want to consider installing their Drupal sites in completely separate areas of the web server that are not able to affect each other via PHP scripting.

Comprehensive Multi-site Video Running a successful multi-site install requires knowledge of how Drupal handles multiple sites and how various features of Apache can be leveraged to make the multi-site much easier to manage. The video at this link provides a great deal of detail and comprehensive conf files for an Apache multi-site install of Drupal Video about Multisites vs. Multiple Sites at GotDrupal.com

Domain Access The Domain Access project is a group of lightweight modules that provide tools for running a group of affiliated sites from one Drupal installation and a single shared database. The module


allows you to share users, content, and configurations across a group of sites such as: - example.com - one.example.com - two.example.com - my.example.com - thisexample.com <-- can use any domain string - example.com:3000 <-- treats non-standard ports as unique By default, these sites share all tables in your Drupal installation. The Domain Prefix module allows for selective, dynamic table prefixing for advanced users. The module uses Drupal's Node Access system to determine what content is available on each site in the network. Unlike other multi-domain modules for Drupal, the Domain Access module determines user access based on the active domain that the user is viewing, rather than which group or site the user belongs to.

Installation and configuration Make sure to install and configure your Drupal site before installing Domain Access. You only need one settings.php file. For detailed instructions, see INSTALL.txt in the module directory. To install the module, simply untar the download and put it in your site's modules directory and enable the module normally. When you enable the module, it will create a {domain} table in your Drupal database. All existing nodes on your site will be assigned to the default domain for your web site and to all affiliates. As of 6.x.2.0, the primary domain is created for you in domain table on installation, previous versions store the primary domain in variables table.

How to add a patch that will synchronize file paths between dev and prod/live sites If you have built your Drupal site on a development machine using multi-site configuration, you've discovered that there's a headache that will occur once you've ported your site to its live box. If you are about to do this, then you'll soon see the problem. The problem lays within the site/files directory. Once you've uploaded your development site to its live server, you'll discover that you have to go into your tables and change the paths for images and other files. On your development box, you named, just for the sake of example, your site: drupal/sites/mygreatwebsite.com And in both of these directories, you've added a files directory. To the world, that site will be: thegreatestwebsite.com and its file structure is drupal/sites/thegreatestwebsite.com Once you went live, you'll see that the paths to your images, pdf and other files looks something like this: http://thegreatestwebsite.com/sites/thegreatestwebsite.com/files/myimage... On one hand, you gulp at the length of that url. Next, you sigh because you realize that you have to change the path -- in the database. Drupal 7 has a fix for this -- not the length (that's another story) but for the path problem. And, while it won't be committed to Drupal 6, there is a patch that you can add to Drupal 6 -- if you don't mind doing that no-no: patching core. You'll find the patch here: http://drupal.org/user/216078 It's called: multisite-d6-2.patch Here are the steps I used to install this on a WinXP machine. To do patching, I use cygwin patch utility. Please change words that are italicized to domains/paths applicable to your site. 1. Place patch in apache2/triad/htdocsfolder 2. In command screen go to apache2triad/htdocs/drupal folder 3. Run the patch: patch -p0 < ../thepatch.patch 4. Make sure there is a files directory located as follows: apache2triad/htdocs/drupal/sites/water/files 5. Production and development sites should BOTH contain: sites/water/files 6. Files system path on both local development box and production site ---- sites/water/files 7. Create a file named "sites.php" and put it into: drupal/sites/ Content of file: <?php $sites = array( 'water.uida.local' => 'water', 'water.uida.edu' => 'water', ); ?>

Note that this file can also be created by copying the default.sites.php file created when the patch was installed. Copy it and rename it to sites.php Open the file and remove the hash marks (#) that are located in front of the array at the bottom of the file. Replace the variables in the array with your configuration. 8. Clear caches. Run update.php if you are updating an already created site.


9. Add a "hacks.txt" file to your site, perhaps, to make sure others know about this core change and how it affects updates. If you are doing this on an established site, you'll need to modify the path for your images/files as they'll be pointing to the old directory under sites.

Multi-site on Linux Software installed: Drupal 6 Apache 1.3 Red Hat 6.1 (Although other distributions will work) PHP5 PostgreSQL 7.4

The process: 1. Install the Drupal core download from Drupal.org. 2. Install default Drupal site. I created mine with a "dummyUSER" user and "dummyDB" database with Drupal code in apache/htdocs/drupal 3. Once you have this working then create a new dir "example1.com" directly under "sites" dir. mkdir sites/example1.com cp -R sites/default/* sites/example1.com/

4. Create a new database "example1DB" and new user "example1USER" in database. 5. Edit the sites/example1.com/settings.php and change the following: $db_url = 'pgsql://dummyUSER:dummyPSSWDl@localhost/dummyDB'; to $db_url = 'pgsql://example1USER:example1PSSWDl@localhost/example1DB';

(this step is optional, normally Drupal will recognise the base url automatically): 6.

# $base_url = 'http://www.example.com'; to $base_url = 'http://www.example1.com'; cp apache/htdocs/drupal/install.php to apache/htdocs/drupal/sites/example1.com/

(Remove the '#' [comment])

7. Point your browser to www.example1.com. Drupal will show you lot of errors. 8. Once it shows you errors you are all good. Just point your browser to www.example1.com/install.php and it will present you the installation screen. Install from there on. 9. Repeat the above process for www.example2.com Note If it says the site is offline then your database information in settings.php is not correct. If it says site not found then your base url in settings.php is not correct.

Multi-site on Mac To get multi-sites working on apache on a Mac, see "HowTo: Configure your local workstation to serve multiple sites using Drupal's multisite configurations and Apache's VirtualHost".

Multi-site on Windows Software This guide assumes you have installed WAMPServer, which you can download below: WampServer - includes Apache, PHP, and MySQL. Alternatively, you can install another WAMP package, but you will need to adjust the paths as necessary.

Process 1. Set up Drupal as usual 1. Install WampServer (or another 'WAMP' package) and make sure that Apache and MySQL services are running. 2. open phpMyAdmin in your browser localhost/phpmyadmin (or it can be launched via the WampServer icon in your system tray.) 3. Create a new database -- we'll call it drupal6 because this will be our base installation which we will leave untouched. While you're here create a second and third database, which we'll call site1 and site2. You can also change the database privileges if you want, but to keep things simple here, we'll stick with the default user root and no password. 4. Download Drupal 6 into WampServer's wamp/www folder (or the htdocs folder in Xampp and some other packages). (You'll need a tool like 7-zip to unpack the Drupal tar.gz file). 5. Do your first Drupal installation by pointing your browser to your Drupal folder, which should be something like http://localhost/drupal-6.x. Enter the drupal6 database and user settings (root and no password in this example). Complete the installation, but then leave it alone. 2. Prepare the files for your new multi-sites


2. Prepare the files for your new multi-sites 1. Find Drupal's sites folder, make a copy of the default folder and rename it to the URL you want for your first test site. To keep it simple, we'll make it the same as the first test site database: site1. While you're here, create files, tmp and themes sub-folders within this new folder. The themes sub-folder will be used for any custom theme you create specifically for this site. Similarly, you can create a modules sub-folder for any module you use only with this site. However, generally, I find it easier to keep all contributed modules together in the sites/all/modules folder so I can use them with all my test sites. 2. Use a text editor to open the settings.php file in your new site1 folder and change $db_url line to reflect your first test site database, and the database user name and password, eg: mysql://username:password@localhost/site1

Also, change the $base_url to the above site URL, eg: $base_url = 'http://site1'; // NO trailing slash! 3. Repeat the above 2 steps for the other test site (site2) 3. Adjust windows so it can find the new site at a new made-up name Find the Windows hosts file, which should be in Windows' System32/drivers/etc folder, make a backup of that file and then open the hosts file in a text editor. Here, add new lines mapping your localhost IP address to match the URLs of your test sites and your original Drupal site. In our example, it would look like this: 127.0.0.1 127.0.0.1 127.0.0.1 127.0.0.1

localhost site1 site2 drupal6

This is telling Windows Networking that http://site1/ is a name for this machine. 4. Adjust Apache so it responds to requests for the new names 1. Find the Apache httpd.conf file which, in WampServer, should be in the wamp/bin/ApacheX.X/conf folder (where X.X is the Apache version number). Sorry, I can't remember where Xampp installs this, but it will be in the Apache folder. Anyway, all you need to do with this file is make sure the "Virtual hosts" line is active by removing the # before Include conf/extra/httpd-vhosts.conf. 2. Make a backup of that httpd-vhosts.conf file (which is in Apache's conf/extra folder), then open httpd-vhosts.conf with a text editor and add these lines: <VirtualHost *:80> DocumentRoot C:/wamp/www/drupal-6.x ServerName site1 </VirtualHost>

where C:/wamp/www/drupal-6.x is the path to the Drupal installation folder and site1 is the URL for your first test site. Add another entry for your other test site, another entry for your original Drupal installation and another entry for WampServer's localhost URL (if you want to keep this active), eg: <VirtualHost *:80> DocumentRoot C:/wamp/www/drupal-6.x ServerName site2 </VirtualHost> <VirtualHost *:80> DocumentRoot C:/wamp/www/drupal-6.x ServerName drupal6 </VirtualHost> <VirtualHost *:80> DocumentRoot C:/wamp/www ServerName localhost </VirtualHost>

3. Restart Apache for the changes to take effect. With WampServer, this is done by selecting the WampServer icon in the system tray and then "Restart all services". 5. Now you're ready to install each Drupal test site using your browser, eg: http://site1/install.php http://site2/install.php

And that's it. You can access your two test sites anytime WampServer is running by using the http://site1 and http://site1 URLs. As I said, it's not exactly simple, but it is well worth the effort. Now you can do whatever you like with your test sites. If you mess up with, say, site1, just delete the site1 folder, the site1 database in phpMyAdmin and the site1 entries in the Windows hosts and Apache httpd-vhosts.conf files and start again. Alternatively, if a site works out and you want to go live with it, the multi-site approach makes this easier (in my opinion). One final caveat: I'm no expert. This technique works for me (apart from one corporate environment with a particularly annoying proxy server), but I'd welcome any feedback, especially if there are any accuracies.

Multi-site with single codebase, different content databases, shared user database, shared sign-on SECURITY ALERT - The Shared Sign-On module this tutorial formerly relied on has been withdrawn because of security risks. It is no longer available at all. There is a replacement module


called Single Sign On. It should replace the Shared Sign-On module, but currently reports problems with user registration and recommends registration happens through the master site. If your solution requires branded registration pages, you could create a suite of registration pages on the master site and use the Theme Key module to apply the correct theme to each one, for a seamless user experience (except for the URL, obviously). WARNING - Following this approach will make updates potentially trickier. It's not a major issue, but you must be mindful that you are sharing some tables amongst several Drupal instances, so you will need to be aware of install and update hooks which want to make changes to any shared tables. For example, it is possible for the update or install scripts of a module unique to one instance to break other instances if it makes changes to shared tables. There are other multi-site solutions, so plan carefully before selecting which one is right for you. This tutorial makes two assumptions: 1. You are _not_ a total newbie 2. You have downloaded MySQL GUI Tools (to make life easier) - clearly this is not a prerequisite - if you know and prefer command line MySQL then you can replicate the steps I provide easily enough: http://dev.mysql.com/downloads/gui-tools/5.0.html Make a copy of /sites/default to /sites/site1 and another to /sites/site2. You may add modules and themes directories in these new directories in the usual way for site-specific configurations. A files directory should be there already but create it if not. Rename /sites/site1/default.settings.php to settings.php and /sites/site2/default.settings.php to settings.php (and, for Linux, make sure both new files directories are writeable by the web user). Add these lines to your hosts files: 127.0.0.1 127.0.0.1

site1 site2

Create three empty MySQL databases: drupal_site1 drupal_site2 drupal_shared_tables

Go to http://site1 and run through the install process in the normal way, using the database name 'drupal_site1'. In MySQL Administrator create a new back-up project of the 'drupal_site1' database containing _only_ the tables you want to share, save it and run it. The result should be a .sql file somewhere on your computer. For reference, the Drupal 6.x tables for sharing users are as follows: authmap profile_fields profile_values role sessions users

The absolute basic essential tables (if you don't want to share any data except for the users and sessions, no profile data, roles, etc.) are: sessions users

IMPORTANT: if you have Open ID authentication enabled you will also need to share the openid_associations table. (This is not a definitive list. Nothing stops you from sharing taxonomy, etc. For example, with this configuration permissions are _not_ shared - each site has its own permissions matrix - but you might want to share one across all sites. Ditto with enabled modules.) Edit the .sql script you created and update the database name within to 'drupal_shared_tables'. Using the Restore interface, run the .sql script. You should now have a database called 'drupal_shared_tables' containing only the list of tables above. Go to http://site2 and run through the install process in the normal way, using the database name 'drupal_site2'. Go back to your original 'drupal_site1' database and drop the same list of tables from the schema. Ditto for the 'drupal_site2' database. Now our databases are ready. Browse to your Drupal application and edit /sites/site1/settings.php. Change these lines (usually starting at line 93 in Drupal 6.3): <?php $db_url = 'mysqli://user:password@localhost/drupal_site1'; $db_prefix = ''; ?>

To: <?php $db_url = 'mysqli://user:password@localhost/drupal_site1';


$db_prefix = array( 'default' => '', 'authmap' => 'drupal_shared_tables.', 'profile_fields' => 'drupal_shared_tables.', 'profile_values' => 'drupal_shared_tables.', 'role' => 'drupal_shared_tables.', 'sessions' => 'drupal_shared_tables.', 'users' => 'drupal_shared_tables.', ); ?>

Note the all important trailing dot on the end of drupal_shared_tables - this is vital! You're prepending table name info to Drupal's default table name, so you need the dot or Drupal will try to select: drupal_site_1.drupal_shared_tablesauthmap

Instead of the desired: drupal_shared_tables.authmap

Do the same for /sites/site2/settings.php, but with $db_url set to drupal_site2. You should now be able to login as the super user on both sites separately (http://site1/user or http://site2/user), using the credentials you entered when you installed site1, give them both different settings, modules, permissions and content but use the same user credentials. Moreover, if you create a user on site1, you will see them in users in site2, but with an entirely independent set of permissions you can decide on a site level. Now for the shared logins. Download and unzip the Single Sign On module in to /sites/all/modules. Enable the Single Sign On Controller module on the site you want to use as the master for logins (the controller) - in our case, site1. Enable the Single Sign On Client module on all other sites, for this example just site2. Then IMMEDIATELY add the following line to settings.php for both sites (see the Single SignOn README) or you will get PHP errors: <?php # Single Sign On settings: $conf['session_inc'] = 'sites/all/modules/sso/session.singlesignon.inc'; ?>

Copy and paste the Controller information on site1 (found at admin/settings/singlesignoncontroller) over to the Client settings on site2 (go to admin/settings/singlesignon-client). Save settings on site2 and logout of both sites. Clear your cookies to avoid any hang-overs from presingle-sign-on days. Go to http://site1/user and login. Now go to http://site2. You should stay logged in. Logout again and see you are logged out in both locations. Reverse it so you login on site2 and visit site1. Neat, huh? =) Need a new site? Take a copy of /sites/default to /sites/new-site, make the necessary vhosts (optional, see below) and DNS/hosts changes, create a blank database, run the Drupal installer, drop the tables you don't want from your new database (the shared ones), edit settings.php adding the $db_prefix array as above, configure Single Sign-On and you're done! Optional Apache settings If you would like to set different Apache settings for your various sites (e.g. different access and error logs for each) add a vhost entry to Apache looking something like this: <VirtualHost *:80> DocumentRoot "c:/path/to/drupal" ServerName site1 ServerAlias site2 <IfModule log_config_module> CustomLog logs/d6_access_log.log common </IfModule> ErrorLog logs/d6_error_log.log <Directory "c:/path/to/drupal"> Options +Indexes AllowOverride All Allow from all </Directory> </VirtualHost>

This is not usually necessary. IMPORTANT - if any of your client sites have any URLs that need to be accessed without an authentication check, the site concerned _must_ have a line in the 'Request paths' box of Single Sign On Client settings to exclude that URL from being redirected to the controller site for authentication. These settings can be found under 'Bot recognition' settings (admin/settings/singlesignon-client). For example, if one of your clients uses the Services module to expose Drupal-based web services you would add a line like this to that box, in order to exclude all your endpoints from the authentication check: services/*


Remember, this only applies to client sites, not the controller, and must be done on all sites.

Multisite Manager module The Multisite Manager contributed module allows creation of new Drupal sites from a central Drupal site without the creator having access to database info. The new site is installed either in the same database with a different prefix or if the drupal db_user has access to create a database and grant privileges, then possibly there. Get started by reading the INSTALL.txt documentation. More help on some gotchas exists below. File names In a situation where you have an existing file for a site http://www.mysite.com and you want to multisite http://www.myothersite.com into it you need to make a new directory to the /sites directory you will find within the mysite file system. This directory should be called /sites/myothersite.com. When you set up a new Drupal site you are asked for the database name, username, password and the prefix of files relevant to the site. This information has to be entered into Database Manager in http://www.mysite.com/admin/settings/multisite_manager and will generate a script This script - which currently does not include the database password and omits the initial <?php - should be copied into a file called settings.php and pasted into the directory you set up above. What Happens Next Log into http://www.myothersite.com normally and the normal Drupal set up screens will appear. Complete this and then create the new site as if it were free standing.

Notes on Multi-sites with Separate Code bases A simple Multi-Site install uses different databases for each site that the installation is hosting. In a nutshell, a simple Multi-Site extends a "Singular-Site" installation by the web developer (that's you) adding folders for each site that is served by the installation, in the "sites" directory.

The "default" Folder When you install a fresh copy of Drupal, a "default" folder is created inside "theRootOfYourInstallation/sites". Inside this folder resides a file called "settings.php" which contains this line of code:$db_url = 'mysql://username:password@pathTodataBase'; to securely access your database. During the initial database installation wizard, Drupal populates that particular line of code with values you entered into the fields. The reason this is called "default" is because, well, this is the default folder that Drupal looks into to find (in the "settings.php" file) they keys to access the databases. The "default" folder can also contain other items, such as a folder called "files" that contains the files for the default installation.

The Key to Understanding Multi-Sites For continuity, I will use "example.com" as the domain name for the "default" installation / folder and "doesthiswork.com" as the domain name for the site we are adding to this installation. Now that you know about the "default" folder, you can begin to understand how to add multiple sites to one installation: to add another site to this installation, 1. Create a sibling of the "default" folder with the domain name of "doesthiswork.com" . It is way important to use the extenstion (the .com or .edu, or .org). 2. Inside this folder, you will include a fresh version of "settings.php" (renamed from a "default.settings.php" taken from the zipped files of fresh Drupal core files) 3. Make sure that you have a fresh database created with no tables in it. Write down the path to the database, the username and password 4. Now you are ready to point your site to the root of the installation. The tricky part of this "simple" instruction is find where your DNS provider lets you point to another site. An example of a solution to this step is this: Say my "default" domain name (that goes to the default folder) is http://www.example.com and a domain /site that I want to add to this installation is http://www.doesthiswork.com. In the DNS panel for http://www.doesthiswork.com, I would point the CNAME to http://www.example.com. When Drupal gets a request for "doesthiswork.com", it will find the doesthiswork.com folder in sites. 5. The first time you go to "doesthiswork.com", you will be greeted with the welcoming Drupal Installation Wizard". Once you finish the installation wizard, the keys to your database kingdom will be saved an you will be free to enter your new Drupal site. So, what's the key to understanding how this works? It is: for every site that you add on this installation, you will need to create a separate domain name folder in sites with (at least) a "settings.php" file. Each folder/settings.php file points to a different database installation.


Drupal uses the files stored on the multisite server, in addition to the data in each database to serve up the site. For each new site that you add, you will have to run through the installation wizard, setting up the site and adding content. (See installation profiles to make your life easier!) To reiterate: when you update a module, you will have to run "http://www.example.com/update.php" and "http://www.doesthiswork.com/update.php" (and other update.php's for each site on your multi-site install) to update EACH database. You might think that this is still alot of work... but it really is a time saver. Remember to: put your sites in maintainence mode (admin/settings/site-maintenance)before run cron manually (admin/reports/status/run-cron) and clear the cache (admin/settings/performance) after you run update.php !

Additional Files to place inside your "default" sibling You can also have Files Theme Modules as folders in the "doesthiswork.com" folders. Those items will only be seen by the "doesthiswork.com" installation. As you might have guessed, items in the "theRootOfYourInstallation/sites/all" folder will be avaliable to ALL the site in your Multi-site install. If you place all your modules in the "theRootOfYourInstallation/sites/all" folder, when you upgrade to an newer version, you only have to replace the module there. However, you need to "update.php" on all sites in your multi-site... including your default site. One of the things that can trip you up is forgetting to create a "files" folder in your "doesthiswork.com" folder. Make sure you have done that to keep separate files from "different" sites.

Multi-site how-tos Instructions on multi-site configuration (also known as "multisite" and "single code base" installations) can be found within the installation instructions. Here are some useful links: Search results 4.6 Multi-Site from single code base Multi-Site .htaccess configuration multi-site, single installation, shared db, single sign on. is this possible? Automatic Multi-site registration Using Multi-Site (4.x) Multihosting Off Single Codebase (4.x) Multisite and symlinks doesn't work Sharing tables between installations (4.x)

10 Minute Multisite Install & Configuration Multisite 10 minute Install: Server: LAMP SSH (telnet) Client: ssh (PuTTY if you are using Windows to access your LAMP host) Must have root access to your server. If website in question is an addon domain, i.e., addon.example.com, then substitute "addon" for "www" in steps below. For list of Linux commands visit: http://www.oreillynet.com/linux/cmd/ or http://www.ss64.com/bash/ Here we go: [login via ssh / PuTTY]

Debian (Ubuntu) Debian and offshoot distributions (e.g. Ubuntu) make installation and configuration very easy. This was done on a Debian distribution, Ubuntu should work identically.

Installation


Assumption: apache2 and mysql are already installed. If not, use apt-get install to install and configure them. # apt-get install drupal6

Answer the questions. This will install everything necessary to run drupal and do the basic configuration, including creating an empty database for drupal. Configure database for drupal6 with dbconfig-common? [YES] Database type to be used by drupal6: [mysql] Password of your database's administrative user: [enter mysql root password] MySQL application password for drupal6: [create a password for your drupal6 db] (enter the password again for verification) You now have a basic unconfigured Drupal6 installation using the database drupal6 and accessible at http://www.example.com/drupal6. Do not use it. This is the "prototype" installation that we will use to create the sites we really want to use.

Create Site Databases The easiest way to create databases for your sites is to use dpkg-reconfigure and answer the questions. # dpkg-reconfigure drupal6

Re-install database for drupal6? [YES] Database type to be used by drupal6: [mysql] Connection method for MySQL database of drupal6: [unix socket] Name of your database's administrative user: [root] Password of your database's administrative user: [enter mysql root password] username for drupal6: [ENTER YOUR DB SITE *USERNAME* HERE (e.g. mysite)] database name for drupal6: [ENTER YOUR DB *SITE* NAME HERE (e.g. mysite)] Notes: 1. Repeat the above for each site you want to support. 2. I used the same name for the database and the site. KISS. 3. Don't use periods (e.g. mysite.com is a bad choice). If you want to spell out the whole name, use underscores instead of periods (e.g. mysite_com). 4. The above method ends up using the same site database password for all the sites you create. Advice: use mysql-admin (or mysql) to use different passwords for each site.

Configure Apache2 for Sites Apache2 needs to be configured to support vhost access to your new sites. Create vhost site configuration files in /etc/apache2/sites-available/ # # Virtual hosting configuration for Drupal # <VirtualHost *:80> ServerAdmin [your email address] DocumentRoot /usr/share/drupal6/ ServerName [your vhost#1 name] ServerAlias [if you want to support www.example.com and example.com] RewriteEngine On RewriteOptions inherit </VirtualHost> <VirtualHost *:80> ServerAdmin [your email address] DocumentRoot /usr/share/drupal6/ ServerName [your vhost#2 name] ServerAlias [if you want to support www.example1.com and example1.com] RewriteEngine On RewriteOptions inherit </VirtualHost> [...repeat for all your vhosts]

Notes: Modify the above items that are in [square brackets]. You likely will want to support port 443 ( https) as well. See Apache documentation for detailed instructions. I've chosen to put all the vhosts in one file named drupal. You may want to use one file per vhost. Sym-link the drupal file in the sites-enabled directory to enable it in your site: # cd /etc/apache2/sites-enabled # ln -s ../sites-available/drupal .

...and reload Apache2 to pick up your configuration changes... # /etc/init.d/apache2 reload


Create Drupal Site Configurations We need to create Drupal configurations for each site by copying the default configuration to the Drupal site subdirectory. # cd /etc/drupal/6/sites/ # cp -a default [site1.com] # cp -a default [site2.com] : :

...and edit the configurations to use the right database, MySQL user name, and password... # vi site1.com/dbconfig.php # vi site2.com/dbconfig.php : :

Notes: Modify the above items that are in [square brackets].

Run Drupal and Configure Your Sites Browse to your sites, running install.php (e.g. http://www.example.com/install.php) to configure them.

Manual Get to location where Drupal core will be located: [/]# cd /var/www

Upload Drupal core: "x.x" should be replaced with the version of Drupal you're installing, e.g. "5.2" [/var/www]# wget http://ftp.osuosl.org/pub/drupal/files/projects/drupalx.x.tar.gz

Unpack Drupal core: [/var/www]# tar -zxvf drupal-5.2.tar.gz

Move contents of Drupal core (including .htaccess) to html: [/var/www]# mv drupal-x.x/* drupal-x.x/.htaccess /var/www/html

Clean-up: [/var/www]# rm drupal-x.x.tar.gz [/var/www]# rm drupal-5.2

Create the files directory per Drupal instructions and change permissions (will change permission again after install): [/var/www]# cd html [/var/www/html]# mkdir files [/var/www/html]# chmod 777 files

Make directories that will hold custom and contributes modules and themes: [/var/www/html]# cd sites/all [/var/www/html/sites/all]# mkdir modules [/var/www/html/sites/all]# mkdir themes [/var/www/html/sites/all]# cd modules [/var/www/html/sites/all/modules]# mkdir custom [/var/www/html/sites/all/modules]# mkdir contrib [/var/www/html/sites/all/modules]# cd ../ [/var/www/html/sites/all]# cd themes [/var/www/html/sites/all/themes]# mkdir custom [/var/www/html/sites/all/themes]# mkdir contrib

Create directory "www.example.com.tld": [/var/www/html/sites/all/themes]# cd ../ [/var/www/html/sites/all]# cd ../ [/var/www/html/sites]# mkdir www.example.com


Change permission of "settings.php" per Drupal instructions and copy "settings.php" in default to www.example.com: [/var/www/html/sites]# cd default [/var/www/html/sites/default]# chmod 777 settings.php [/var/www/html/sites/default]# cd ../ [/var/www/html/sites]# cp -a default www.example.com

Create database and user with permissions: [/var/www/html/sites]# mysql mysql> CREATE DATABASE wwwexamplecom_drupal; mysql> GRANT ALL PRIVILEGES ON wwwwexamplecom_drupal_drupal.* TO 'wwwexamplecom_drupal_myusername'@'localhost' IDENTIFIED BY 'mypassword';

mysql> \q Go back to PuTTY to chmod on settings.php in www.example.com: [/var/www/html/sites]# cd www.example.com [/var/www/html/sites/www.example.com]# chmod 755 settings.php [/var/www/html/sites/www.example.com]# logout

What next?: Make changes to "settings.php" in www.example.com? I've read that it's not necessary to make changes to setting.php. Make changes to "httpd.conf" in /usr/local/apache/conf? I've been using WHM to create accounts, putting Drupal in public_html and having no problems. But when it comes to parting from the WHM abstraction and getting multisite set-up correctly this is the end of the proverbial road for me. Could use help...: 1. Help making improvements to steps articulated above 2. Help with next steps so that I/we can get on to grander things, like creating modules, custom themes, profiles, (and maybe - just maybe - spend some time working on business strategy :)

Setting up multi-site Drupal on Windows or Linux - consolidated instructions If you are new to Drupal and Apache, these instructions will walk you through most of the things you need to do, to get Drupal up and running on Linux or Windows. I haven't covered the details of setting up a database in MySQL with phpMyAdmin, but I do show you how to configure Apache so that this works. For the Linux instructions the file paths are typical for SuSE Linux 10.1 - for most other Linuxes the htdocs path is likely to start at a different place, but the principles are the same. We will assume you want to make a local installation on a Windows PC and an installation reachable via the Internet on a Linux server.

Hosts file You will find the hosts file at the following locations: - on Windows: c:\windows\system32\drivers\etc\hosts - on Linux: /etc/hosts

In these instructions, we will assume you want to install a number of sites at: On Windows (local): On Linux (Internet):

strawberry.loc and blueberry.site yourdomain.com and blueberry.com

The hosts file will look as follows for the Windows example: 127.0.0.1 127.0.0.1 127.0.0.1

localhost strawberry.loc blueberry.site blueberry

For a Linux internet server the hosts file should look as follows: ipaddress 127.0.0.1

yourhostname.yourdomain.com localhost

yourhostname

Please note that for the Linux server on the Internet you only need to have the Fully Qualified Domainname (FQD) in the form of yourhostname.yourdomain.com in the hosts file. On a Linux system working with DNS, you DO NOT need to make any entries other than the primary FQD in the hosts file. If you want to have a local site at blueberry.site on Linux, you should add the


following line to the Linux hosts file: 127.0.0.1

blueberry.site blueberry

Of course blueberry.site would not be reachable through the Internet, because the Internet DNS system would know nothing about it, whereas it would work for a local user on that computer because the local system would know how to deal with it by looking up the information in the computer's hosts file.

DNS Set up the DNS entries for every domainname your server is hosting. Each one of these domains needs to be resolved to your server's ip address. You will normally be able to configure this with your domain registrar or hosting provider. For a local Windows or Linux installation, e.g. for development, no DNS configuration is necessary - the entries in the hosts file on your computer are sufficient. Please note that the default localhosts entry in the hosts file is not sufficient to get multi sites working on a Drupal installation.

Apache Configure Apache for all the different domain names that your web server will be serving. For Linux (our examples use paths for SuSE Linux > 10.0) the best place to do this is in the vhosts.conf file in the /etc/apache2/vhosts.d directory (you can call this file {anything}.conf). Actually every filename ending in .conf in the vhosts.d directory will be picked up, so beware of multiple overlapping entries - they will confuse the web server. The entries can also be placed in the /etc/apache2/httpd.conf file initially to keep things simple. Later it is better to do it in the vhosts.conf file - this way, in case of an update of the Apache software, if the httpd.conf file is overwritten, your configuration will survive. For Windows, you can get Apache, MySQL and PHP from: http://www.apachefriends.org/en/xampp-windows.html

If you installed XAMPP in the default directory on Windows, the vhosts file will be at: c:\program files\xampp\apache\conf\extra\httpd-vhosts.conf

The entries can also be placed in the c:\program files\xampp\apache\conf\httpd.conf

file initially to keep things simple.

On Linux (the paths in the example are for SuSE Linux), your vhosts.conf file (or entries at the end of the httpd.conf file) should look as follows: # # First entry of this file should be the NameVirtualHost entry, unless it is already there # If you want your web server to respond on a different port from the default # replace 80 with that port number # Available alternative port numbers are 8080 and 2222 # NameVirtualHost *:80 # For each domain you want to serve up with Drupal, # you need an entry that will match the incoming domain # You do not need a separate entry for each subdomain - Drupal takes care of that # The following entry takes care of : # yourdomain.com # www.yourdomain.com # bla.yourdomain.com # gaga.yourdomain.com # etc. # # In the following we assume that the Apache htdocs directory is at /srv/www/htdocs # and that Drupal has been installed in /srv/www/htdocs/drupal # <VirtualHost *:80> DocumentRoot /srv/www/htdocs/drupal ServerName yourdomain.com ServerAlias *.yourdomain.com <Directory /srv/www/htdocs/drupal> Allow from all Options +Includes +Indexes +FollowSymLinks AllowOverride all </Directory> </VirtualHost> # # The following entry takes care of : # blueberry.com # www.blueberry.com # bla.blueberry.com # gaga.blueberry.com # etc. # # In the following we assume that the Apache htdocs directory is at /srv/www/htdocs # and that Drupal has been installed in /srv/www/htdocs/drupal # <VirtualHost *:80> DocumentRoot /srv/www/htdocs/drupal


ServerName blueberry.com ServerAlias *.blueberry.com <Directory /srv/www/htdocs/drupal> Allow from all Options +Includes +Indexes +FollowSymLinks AllowOverride all </Directory> </VirtualHost> # # On Linux, you will also need an entry to ensure that your phpMyAdmin works # - you will need phpMyAdmin to set up and manage your MySQL databases # I assume here that you have unpacked and copied the phpMyAdmin files to # /srv/www/htdocs/phpMyAdmin # Please note that in the following, the DocumentRoot and Directory command # both need to use phpMyAdmin # using phpmyadmin doesn't work even though phpmyadmin # is a symbolic link to phpMyAdmin # <VirtualHost *:80> ServerName phpmyadmin.yourdomain.com DocumentRoot /srv/www/htdocs/phpMyAdmin <Directory /srv/www/htdocs/phpMyAdmin> allow from all Options +Indexes +Includes +FollowSymLinks AllowOverride FileInfo Options </Directory> </VirtualHost>

For the local Windows example, your httpd-vhosts.conf file (or entries at the end of the httpd.conf file) should look as follows: # # First entry of this file should be the NameVirtualHost entry, unless it is already there # If you want your web server to respond on a different port from the default # replace 80 with that port number # Available alternative port numbers are 8080 and 2222 # NameVirtualHost *:80 # For each domain you want to serve up with Drupal, # you need an entry that will match the incoming domain # You do not need a separate entry for each subdomain - Drupal takes care of that # # The following entry takes care of e.g: # strawberry.loc # www.strawberry.loc # bla.strawberry.loc # gaga.strawberry.loc # etc. # # In the following we assume that the Apache htdocs directory is at # c:/program files/xampp/htdocs # and that Drupal has been installed in c:/program files/xampp/htdocs/drupal # <VirtualHost *:80> DocumentRoot "c:/program files/xampp/htdocs/drupal" ServerName strawberry.loc ServerAlias *.yourdomain.com <Directory "c:/program files/xampp/htdocs/drupal"> Allow from all Options +Includes +Indexes +FollowSymLinks AllowOverride all </Directory> </VirtualHost> # # # # # # # # # # #

The following entry takes care of e.g: blueberry.site www.blueberry.site bla.blueberry.site gaga.blueberry.site etc. In the following we assume that the Apache htdocs directory is at c:/program files/xampp/htdocs and that Drupal has been installed in c:/program files/xampp/htdocs/drupal

<VirtualHost *:80> DocumentRoot "c:/program files/xampp/htdocs/drupal" ServerName blueberry.site ServerAlias *.blueberry.site <Directory "c:/program files/xampp/htdocs/drupal"> Allow from all Options +Includes +Indexes +FollowSymLinks AllowOverride all </Directory> </VirtualHost>

It seems that the first entry in vhosts.conf becomes the default Apache server, whereas the last entry in httpd.conf becomes the default. On Windows, you get to phpMyAdmin by pointing your browser to http://localhost/phpmyadmin, if it is installed in the default location under the xampp directory.


Drupal Make sure that the settings.php file in /srv/www/htdocs/drupal/sites/default on Linux or c:\program files\xampp\htdocs\drupal\sites\default on Windows

is the default file that came with the Drupal distribution. Now point your browser to http://yourdomain.com and you will see the Drupal install screen which will walk you through the installation for the site http://yourdomain.com. It will ask you for - the name of the MySQL database - the database user and password, and - ask you to set up the first user who will become the administrator of the site. It is IMPORTANT that the you use this original settings.php file, OR ELSE the Drupal install procedure DOES NOT WORK when you go to http://yourdomain.com. The reason for this is that if you use an existing installations settings.php file, Drupal thinks the site is already configured and DOES NOT start the one time start up installation. Do the same for any other domains you intend to use and have configured in Apache.

Independent Site at a subdomain To have http://bla.yourdomain.com (Linux example) or http://bla.strawberry.loc (Windows local) be a different Drupal site, create a directory called: /srv/www/htdocs/drupal/sites/bla.yourdomain.com on Linux or c:\program files\xampp\htdocs\drupal\sites\bla.stawberry.loc on Windows

and copy the default settings.php file that came with the Drupal distribution into the above directory. It is IMPORTANT that the you use this original settings.php file, OR ELSE the easy Drupal install procedure DOES NOT WORK. Make sure you have created a separate database for this site in MySQL before starting the install. Just point the browser to: http://bla.yourdomain.com on Linux hosting server or http://bla.strawberry.loc on your Windows server

to start the installation. Again, It will ask you: - for the name of the MySQL database - for the database user and password, and - to set up the first user who will become the administrator of the site. Repeat the above for any other sub-domains you want to configure such as http://hello.blueberry.site on Windows.

Independent site at a sub URL To set up a separate Drupal site at http://www.yourdomain.com/gugu create a directory called /srv/www/htdocs/drupal/sites/www.yourdomain.com.gugu c:\program files\xampp\htdocs\drupal\sites\www.stawberry.loc.gugu on Windows

and copy the default settings.php file that came with the Drupal distribution into the above directory. It is IMPORTANT that the you use this original settings.php file, OR ELSE the Drupal install procedure DOES NOT WORK. Make sure you have created a separate database for this site in MySQL before starting the install. Just point the browser to: http://www.yourdomain.com/gugu on Linux or http://www.strawberry.com/gugu on Windows

to start the installation. You will be asked: - for the name of the MySQL database - for the database user and password, and - to set up the first user who will become the administrator of the site.

Mechanism for setting up sub-domain and sub-directory/sub-URL sites For details on how Drupal uses the sites directory structure, read the INSTALL.txt file in the top level drupal directory. Here is the relevant excerpt:

MULTISITE CONFIGURATION A single Drupal installation can host several Drupal-powered sites, each with its own individual configuration. Additional site configurations are created in subdirectories within the 'sites' directory. Each subdirectory must have a 'settings.php' file which specifies the configuration settings. The easiest way to create additional sites is to copy the 'default' directory and modify the 'settings.php' file as appropriate. The


new directory name is constructed from the site's URL. The configuration for www.example.com could be in 'sites/example.com/settings.php' (note that 'www.' should be omitted if users can access your site at http://example.com/). Sites do not have to have a different domain. You can also use subdomains and subdirectories for Drupal sites. For example, example.com, sub.example.com, and sub.example.com/site3 can all be defined as independent Drupal sites. The setup for a configuration such as this would look like the following: sites/default/settings.php sites/example.com/settings.php sites/sub.example.com/settings.php sites/sub.example.com.site3/settings.php When searching for a site configuration (for example www.sub.example.com/site3), Drupal will search for configuration files in the following order, using the first configuration it finds: sites/www.sub.example.com.site3/settings.php sites/sub.example.com.site3/settings.php sites/example.com.site3/settings.php sites/www.sub.example.com/settings.php sites/sub.example.com/settings.php sites/example.com/settings.php sites/default/settings.php If you are installing on a non-standard port, the port number is treated as the deepest subdomain. For example: http://www.example.com:8080/ could be loaded from sites/8080.www.example.com/. The port number will be removed according to the pattern above if no port-specific configuration is found, just like a real subdomain. Each site configuration can have its own site-specific modules and themes in addition to those installed in the standard 'modules'and 'themes' directories. To use site-specific modules or themes, simply create a 'modules' or 'themes' directory within the site configuration directory. For example, if sub.example.com has a custom theme and a custom module that should not be accessible to other sites, the setup would look like this: sites/sub.example.com/: settings.php themes/custom_theme modules/custom_module NOTE: for more information about multiple virtual hosts or the configuration settings, consult the Drupal handbook at drupal.org.

Permissions FINALLY, make sure all the directories from /srv/www/htdocs/drupal downwards are accessible by the web server. On SuSE Linux, execute: chown -R wwwrun:www /srv/www/htdocs/drupal

to ensure that the directories have the right permissions. The userid (uid) is wwrun and the group id (gid) is www. These can also be configured somewhere in Apache. For other Linux distributions, find out as what user and group Apache runs as and where the htdocs Apache web server root directory is. Nothing further can stand in the way of your Drupal development - enjoy.

5 minute multisite using Cpanel You want petermoulding.biz, cjm.net.au, and unlevelhome.com on the same server, or virtual private server, using one copy of Drupal and a separate database for each site. You are using Cpanel. The first site will take whatever time you need to upload and install Drupal. The second and subsequent sites will take 5 minutes each. This example uses Drupal 6. Drupal 7 appears to be the same.

First site I assume you have the first site set up. You used WHM to create a database account for petermoulding.biz then went into Cpanel for petermoulding.biz to set up email and install Drupal. You now have petermoulding.biz working with Drupal 6. When you created petermoulding.biz, you created a user for the account and I will assume the user is named pb. Use the MySQL Databases option or the wizard. I use the MySQL Databases option. You create a database named drupal and cpanel shows a database named pb_drupal. You create a user named drupal for the database and Cpanel displays user pb_drupal. You then connect user pb_drupal to database pb_drupal with all privileges.


You use the Cpanel Email Accounts page to create some email accounts for petermoulding.biz including one named boss. You will use boss (at) petermoulding.biz as the email address when you create the administration account during the Drupal installation. When installing Drupal, you create directories /sites/default/ and /sites/default/files/ plus file /sites/default/settings.php. You set the permissions to 777 then run the installation step then set /sites/default/settings.php back to 644. You will repeat this step once for each new site.

Second site Go into Cpanel on petermoulding.biz and park domain cjm.net.au on petermoulding.biz. While you are there, park unlevelhome.com on petermoulding.biz. If you run into a limit on the number of parked domains, you can go back into WHM to increase the limit. You want a database per domain so while in WHM, increase the limit on the number of databases. Create a database and database user for cjm.net.au, perhaps user pb_cjm connected to database pb_cjm. Repeate for unlevelhome.com with user pb_unlevel connected to database pb_unlevel. Go into Email Accounts and create boss (at) cjm.net.au plus boss (at) unlevelhome.com. You probably used FTP to upload Drupal 6 to petermoulding.biz. I will assume FTP. There is a file manger in Cpanel that does similar things to Filezilla and other FTP programs. Go into /sites/ and create directory cjm.net.au. While in /sites/, create directory unlevelhome.com. Create directories /sites/cjm.net.au/files/ and /sites/unlevelhome.com/files/ with permission 777. Copy /sites/default/default.settings.php to /sites/cjm.net.au/ and rename the file to settings.php then give it permission 777. Copy /sites/default/default.settings.php to /sites/unlevelhome.com/ and rename the file to settings.php then give it permission 777. Do you notice how you are repeating part of the original installation? Yeah, it gets boring if you have 2000 sites. Visit http://cjm.net.au/ and up pops the Drupal 6 installation process, exactly the same as happened with petermoulding.biz. Answer the same questions. Use the right database name, database user name, and password. Visit http://unlevelhome.com/ and up pops the Drupal 6 installation process, exactly the same as for petermoulding.biz and cjm.net.au.

Third site Done! You could do ten at a time if you can keep track of all the passwords. Replace your Drupal Beginner t-shirt with the Drupal Do It In My Sleep t-shirt.

Themes Put standard themes in /sites/all/themes/ to let every site use the themes. Site specific custom themes go in a /themes/ directory within the site. A cjm theme would go in /sites/cjm.net.au/themes/ as /sites/cjm.net.au/themes/cjm/.

Modules You have a separate database for every site so you can activate different modules for each site. Put the modules in /sites/all/modules/ then let each site switch them on or off.

Update notifications Drupal automatically checks for code updates unless you turn off update notifications. You have only one copy of the code so switch off update notifications for all sites after the first. Ok, darumaki pointed out the case where different sites use different modules. "This might be fine if every site was using the same modules, however, a typical multi-site setup usually involves each site using different modules, therefore, it should not be advised to turn off updates on the other sites, because updates only checks the modules that are enabled and not all the modules in the module directory." You could turn on every single module in your base domain, to get update notifications, or you could turn on updates in just those domains with something different. The same consideration applies to themes because theme updates are detected and notified the same as module updates. The main saving from turning off updates is when you set up 3550 identical sites for the 3550 members of a professional association. They want separate databases for client privacy but they all get the same modules and features as specified by their association.

Maintenance You have one copy of the code so put all the sites offline before updating the code. You have a database for every site so run update.php on every site before switching the sites back online.

Which site goes first?


The first site is locked in place while any other sites are parked on top. Use a throwaway site for the first site. When I registered petermoulding.com, I registered the .biz and some other variations for experiments. I used the .biz domain name as the base site for the multisite setup because the .biz domain name will not be visited by anyone but me. Each time I load a new theme into /sites/all/themes/, I can activate it in petermoulding.biz for testing. When one of the added on sites becomes to busy for the server, I can move the added on site without disturbing the other sites. The first site is also the default site your customers will see when there is a setup failure in Drupal or Apache. Put contact information on the first site so people can report failed Web sites.

Cpanel interface Cpanel provides several themes and each theme shows different combinations of buttons. I use crimson_smoke for these screen shots.

Domains Go to the domains section to park your new domain, unlevelhome.com, to your existing domain, petermoulding.biz. Do not use Subdomain or Addon domain for this part of the setup.

Subdomains You add subdomains in the form x.example.com. For domain unlevelhome.com, you might add forums.unlevelhome.com and shop.unlevelhome.com. First you would park unlevelhome.com on petermoulding.biz and create unlevelhome.com in Drupal following the instructions on this page then you would look at adding forums and shop to unlevelhome.com. Adding subdomains should be covered in another page because there would be considerations around sharing user ids across the domains. The following image shows the Cpanel subdomain page. You can add, edit, and delete subdomains. If you leave the document root field empty, the result will be similar to parking a domain and should let you use multisite. Multisiting forums.unlevelhome.com next to unlevelhome.com would require public_html/sites/forums.unlevelhome.com/settings.php, another database, and a way to share the login.

Addon domains Addon domains are similar to parked domains but add more complications. Do not use Addon domains for the procedure in this page, use Parked domains. The following image shows the Cpanel domain addon page. Notice the password field. Addon domains have a subaccount type arrangement you might use if you want to delegate administration to someone else. You could put several domains in one WHM account using separate subdirectories and give each site administrator separate FTP access. The separate FTP access does not work with multisite because they all share the same directories. Instead use Parked domains and control uploading of files using a Drupal module. If you insist on using Addon domains instead of Parked domains then set Docuement root to public_html/. Drupal multisite is based on all page requests from all sites going to the same public_html/index.php.


Parked domains You can park domain egjhnbhniu.com on example.com and Drupal can separate the two if they have separate multisite settings. You use Parked domains without redirection. A request for egjhnbhniu.com/food/turnip goes to public_html/index.php then Drupal looks for public_html/sites/egjhnbhniu.com/settings.php and uses the database from the settings file. If public_html/sites/egjhnbhniu.com/settings.php is missing, Drupal uses public_html/sites/defaults/settings.php and you see a page from example.com. The following image shows the simple Cpanel domain parking page. It is simple because their are no passwords or document root settings. This is all you need for multisite.

Redirects A redirect sends one domain to somewhere on another domain. egjhnbhniu.com could be redirected to example.com/test-sites/egjhnbhniu/. A common use is for personal Web sites. You create example.com/~george then, for your birthday, grandma buys george.com for you. You can redirect george.com to example.com/~george. When visitors visit george.com, they see example.com/~george so do not redirect, add george.com as a subdomain with a document root of ~george.


Drupal in a subdirectory When you install Drupal in /home/example/public_html/, unlevelhome.com is in /home/example/public_html/sites/unlevelhome.com/ and you access Drupal as http://unlevelhome.com/. If you install Drupal in a subdirectory, for example /home/example/public_html/drupal-6.13/, you can access Drupal as http://unlevelhome.com/drupal-6.13/. An alternative is to change the way you park the domain. Put /drupal-6.13 the Domain Root setting. Set Domain Root to /public_html/drupal-6.13 and access Drupal as http://unlevelhome.com/.

Drupal lookup documentation There are lots of questions about the Drupal lookup sequence to get from a domain name to a settings.php file. The Drupal documentation is in INSTALL.txt under the heading MULTISITE CONFIGURATION. The following list is the lookup sequence from Drupal 6.12 INSTALL.txt for www.sub.example.com/site3. Note that Drupal will not look in sites/sub/settings.php or sites/site3/settings.php. sites/www.sub.example.com.site3/settings.php sites/sub.example.com.site3/settings.php sites/example.com.site3/settings.php sites/www.sub.example.com/settings.php sites/sub.example.com/settings.php sites/example.com/settings.php sites/default/settings.php Attachment

Size

cpanel-domains.png

14.89 KB

cpanel-subdomains.png

12.98 KB

cpanel-addon-domains.png

16.95 KB

cpanel-parked-domains.png 14.3 KB cpanel-redirects.png

17.45 KB

Access all multisites with www. only [.htaccess] The default .htaccess RewriteRule to redirect every multisite.com to www.multisite.com does not work properly. If you have problems with that too, just leave the two lines uncommented, and ADD THE


FOLLOWING 3 LINES FOR EVERY MULTISITE you set up: RewriteCond %{HTTP_HOST} mysite\.com$ [NC] RewriteCond %{HTTP_HOST} !^www\.mysite\.com$ [NC] RewriteRule ^(.*)$ http://www.mysite.com/$1 [L,R=301]

(Thanks to Florian!)

Drupal as a library Short introduction on how to share drupal with all users on a shared server (without having troubles with file-permissions). Note: it is still possible to use the Multisite option of drupal. example server's file-structure: /path/to/phplibraries/drupal (chmod 644) /home/ user1/ (example.com) lib (symbolic link => /path/to/phplibraries/) public_html/ main/ files/ includes (symbolic link => /home/user1/lib/drupal/includes) misc (symbolic link => /home/user1/lib/drupal/misc) modules (symbolic link => /home/user1/lib/drupal/modules) profiles (symbolic link => /home/user1/lib/drupal/profiles) sites/ all (symbolic link => /home/user1/lib/drupal/sites/all) default (symbolic link => /home/user1/lib/drupal/sites/default) example.com/ settings.php themes (symbolic link => /home/user1/lib/drupal/themes) .htaccess (copy from drupal/.htaccess) cron.php (copy from drupal/cron.php) index.php (copy from drupal/index.php) install.php (copy from drupal/install.php) robots.txt (copy from drupal/robots.txt) update.php (copy from drupal/update.php) xmlrpc.php (copy from drupal/xmlrpc.php) .htaccess (1) user2/ (example2.com) (same as user1) user3/ (example3.com) (same as user1) etc...

.htaccess (1) this isn't neccessary but it keeps public_hml/ clean when several sites are maintained by one user. When only one site is maintained the contents of the main/ directory could be moved to public_html/ itself Options +FollowSymLinks DirectoryIndex index.php <IfModule mod_rewrite.c> RewriteEngine on RewriteCond %{REQUEST_FILENAME} RewriteCond %{HTTP_HOST} RewriteRule ^(.*) </IfModule>

!-f ^example\.com$ [NC] main/$1 [L,QSA]

Installing virtual hosts for Drupal sites and subsites The purpose of this guide is to concisely summarize how to implement apache vhost settings for each site or subsite. All steps for this tutorial were implemented with Debian using Apache version 2.0.54. 1. Gain entrance into your shell system, and su to root or another user that can edit apache2 configuration files. 2. Go to your apache configuration files: cd /etc/apache2/sites-available

3. Create a configuration file for your new site. For an example site, www.example.com, we make the site as such: nano example.com <VirtualHost *:80> ServerAdmin me@myserver DocumentRoot /home/web/drupal/ ServerName www.example.com ServerAlias example.com *.example.com RewriteEngine On RewriteOptions inherit


CustomLog /var/log/apache2/example.com.log combined </VirtualHost> <VirtualHost *:443> ServerAdmin me@myserver DocumentRoot /home/web/drupal/ ServerName www.example.com ServerAlias example.com *.example.com RewriteEngine On RewriteOptions inherit CustomLog /var/log/apache2/example.com.log combined # SSL Specific options SSLEngine on SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL SSLCertificateFile /etc/ssl/apache/CA.crt SSLCertificateKeyFile /etc/ssl/apache/CA.key SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown </VirtualHost>

4. Activate the site's configuration: a2ensite www.example.com

5. Reload Apache's configuration: /etc/init.d/apache2 force-reload

Done! If you get an apache error '/home/www/drupal5/.htaccess: Option Indexes not allowed here' Assuming that my apache2 DocumentRoot is at /home/www, and that drupal is linked to the doc root by ln -s /usr/share/drupal5 /home/www/ add the following to /etc/apache2/conf.d/drupal.conf <Directory /home/www/drupal5/> Options +FollowSymLinks Indexes AllowOverride All order allow,deny allow from all </Directory>

Multi-Site, Single Codebase, Shared Database, Shared Sign-on 5.x This document is written with assumptions that you have: installed Drupal 5 before access to your server via ssh / telnet access to MySQL database / you can setup your own MySQL database In this example, I will setup two sites using: Drupal 5 One database on MySQL Linux-based server Also: Website’s url is: www.example.com Drupal is installed in: /var/www/drupal Names of sites are: ‘site_1’ and ‘site_2’ These sites will share session and user-related tables so once users login to one of the sites, they can view the other site without logging in. User roles and profiles are also shared among the two sites. And to be clear, "shared sign on" means that when you created a user/pass on one site, it will work on the other site(s). It doesn't mean that you'll be automatically logged in to all sites at once (if that's what you want there are two modules to help: singlesignon and fierce_sso.)

1. Prepare database and database user 1.1. create a database and user In this example, I will use: DB name: drupal_db DB username: druser Password: twosites Access right: SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES

2. create and modify site configuration 2.1. duplicate settings folder Duplicate a folder contains site config information under drupal/sites/


dru@/var/www/drupal/sites: cp –R default/ www.example.com.site_1 dru@/var/www/drupal/sites: cp –R default/ www.example.com.site_2

2.2. Modify config files 2.2.1. provide DB connection detail Open settings.php in a folder under www.example.com/site_1. Go to line 93 (or somewhere around there) and you should find a line that goes: $db_url = 'mysql://username:password@localhost/databasename';

change this line with your DB name, DB user and password. It will look like: $db_url = 'mysql://druser:twosites@localhost/drupal_db';

2.2.2. set prefixes for table names just below the line we just changed above, there is a line that goes like: $db_prefix=’’;

Since we want to share a database among two sites but not completely, we decide which tables to share / not to share. This can be done by adding prefixes to table names. An example given below is for sharing user-related tables, sequences (this table is a ‘counter’ of users, nodes and other stuff) and session information. $db_prefix = array( 'default' => 'site_1_', 'users' => 'shared_', 'sessions' => 'shared_', 'role' => 'shared_', 'authmap' => 'shared_', 'sequences' => 'shared_', 'profile_fields' => 'shared_', 'profile_values' => 'shared_', 'users_roles' => 'shared_', );

the second line that goes: 'default'

=> 'site_1_',

this means tables that are not shared with other sites will have a prefix ‘site_1_’. So for example, table name ‘node’ will be ‘site_1_node’. Now, when you finish editing this file, open and edit drupal/sites/www.example.com.site_2/settings.php. This time, you enter the exact same information for the database connection. So the line around 93 should look like: $db_url = 'mysql://druser:twosites@localhost/drupal_db';

Then, you add an array of prefixes, which will look like: $db_prefix = array( 'default' => 'site_2_', 'users' => 'shared_', 'sessions' => 'shared_', 'role' => 'shared_', 'authmap' => 'shared_', 'sequences' => 'shared_', 'profile_fields' => 'shared_', 'profile_values' => 'shared_', 'users_roles' => 'shared_', );

notice a prefix in the second line is now ‘site_2_’. At the installation, all tables will have this prefix except for the ones that are specifically given a prefix ‘shared_’.

3. create static links In order to access site_1 and site_2 from web browser, you have to make static links to drupal directory. In the directory /var/www, enter the following commands: ln -s drupal site_1 ln -s drupal site_2

now access ‘www.example.com/site_1’ (and ‘site_2’). If you see a drupalicon and a message like ‘Unable to select database’, go back to 2.2.1 of this manual and check if you have all details correct, including a single-quotation and semicolon. If you see hundreds of messages like ‘Warning: Table 'continuu_test.access' doesn't exist’, you are almost there. While this may look scary or makes you think you have done something wrong, it is just saying that it cannot find tables Drupal needs to have access to. This is simply because we have not yet finished installing Drupal. All the necessary tables will be created in the next step, which is described below.

4. install drupal Since we have entered information for database connection, we can run the install script


(install.php) straight away, instead of giving all the detail to installation wizard. From your web browser, just access: http://www.example.com/site_1/install.php http://www.example.com/site_2/install.php Installation should complete within a second (or two). If you fail to install: read the error message and find out what is wrong (wrong DB name, username or password, etc.) make sure $db_url (around line 93 in settings.php) ends with a quotation mark and semicolon. ( ‘; ) If you have two separate Drupal sites and are thinking about merging them with above method, I would recommend you not to do it. Yes, it is not impossible. You can rename tables and modify values in sequence table and so on, but i tried it and had a hell of a hard time... If you have sites already setup, using CAS or other authentication services is more appropriate than merging.

Multi-Sites Using XAMPP on Windows XP This procedure allows you to create multiple sites sharing one common set of Drupal core modules, and has been tested on Windows XP (SP2), Apache 2.2.3, MySQL 5.0.24a, PHP 5.1.6, Drupal 5.2..... I'm assuming that you have a working Drupal installation already. Please note that this hasn't been tested with XAMPP 1.7.1 (MySQL 5.0.51a, PHP 5.2.9 Apache 2.2.11) and Drupal 6.x! 1. Create a new MySQL database with appropriate rights. eg. mydb 2. Create a new folder in your sites folder by copying the default folder and then renaming it with the name of your new site. eg. mysite 3. Create the following sub-folders in the above folder for stuff unique to your new site. mysite\files mysite\modules mysite\themes mysite\tmp 4. Edit the database and base URL in the settings.php file in the folder you have just renamed. $db_url = 'mysql://username:password@localhost/mydb'; $base_url = 'http://mysite'; // NO trailing slash! 5. Create a new host in the Windows XP hosts file with same name as new folder. 127.0.0.1 mysite 6. Create a new Virtual Host in the Apache httpd-vhost.conf file with forward slashes in the full pathname (include <> tags as per the examples in the file). VirtualHost *:80 DocumentRoot "C:/Program Files/xampp/htdocs/drupal" ServerName mysite VirtualHost 7. Restart the Apache server to load the virtual hosts. 8. Point your web browser at new folder and install Drupal. http://mysite/install.php 9. Create your first user and Administer the File System paths to match step three above. sites/mysite/files sites/mysite/tmp You can use the Windows Search to find the above files if your not familiar with their locations. Good luck!

Multi-site setup using CPanel After some searching around, I found out how to do multi-site setup using CPanel. 1. Create your initial Drupal site at www.example.com. Drupal 5.x makes this very easy to do, as it will automatically install the database for you -- all you have to do is input the database information. 2. Create a folder in the sites/ folder that matches the subdomain you want to create (so for site1.example.com, the folder name would be site1.example.com).


3. Duplicate the settings.php file from the default folder and place it in your newly created folder from step 2. 4. Edit the settings.php file you just placed in the site1.example.com folder so that it does not point to a database. You can replace the line that has your other site's database information with this: $db_url = 'mysql://username:password@localhost/databasename';

This will allow step 9 below to load your database tables. 5. In CPanel, set up the subdomain. For the above example of site1.example.com, a folder named site1 will be automatically created by CPanel. 6. Delete the folder that was created. 7. Use the following example to create and upload a php file onto your site. An easy way to do this is to use Notepad, pasting in the text below, editing as necessary, and then saving as multisite.php. Afterwards, upload via FTP or CPanel's File Manager. Make sure you change the path to whatever the path to your web root is. <?php symlink( '/home/username/public_html/', 'site1' ); ?>

8. Go to the location of the php file in your browser (such as www.example.com/multisite.php) This will create a symlink, allowing your subdomain to work properly. 9. Go to your subdomain. Drupal will ask for your database information and then install the tables necessary to run the new site. 10. You're all set to use the new site. If you run into any problems, or have questions, use the contact form attached to my user account here to contact me.

truly simple cPanel multsite setup No single multisite setup method above got me all the way there; if you have cPanel and Drupal installed on your original domain, follow these straightforward instructions to set up a multisite: For 'www.yourlovelysite.com': 1. in cPanel, go to 'Subdomains' 2. enter a name for you subdomain; this is followed by '.' and your original domain name automatically, e.g. 'sub1' becomes 'sub1.yourlovelysite.com'. 3. enter a 'Document Root'; this needs to point to the folder in your directory containing the 'index.php' file that a Drupal installation creates, e.g. 'public_html', and Create. Please note cPanel may automatically insert "public_html/sub1" - delete "sub1". 4. in cPanel, go to 'MySQL Databases'. 5. name a new database and 'Create Database'. 6. scroll down to 'MySQL Users' and 'Add New User' - and insert a username and password (twice) - give the user all privileges. Make a note of username/password. 7. in 'Add User to Database', associate your new username with the name of the database just created. 8. in cPanel, go to 'File Manager' - though this can be done using Dreamweaver or other ftp software - create a new folder in the 'sites' folder, which is in the root of the Drupal installation, named after your subdomain, e.g. 'sub1.yourlovelysite.com'. 9. copy the file 'settings.php' from the 'sites/default' folder, which you should have already from your original Drupal install, and paste it into your new subdomain folder 'sub1.yourlovelysite.com'. 10. grant this new 'settings.php' all permissions. 11. open 'settings.php' and locate the line "$db_url = 'mysql://username:password@localhost/databasename';". 12. replace each 'variable' with the respective thing, e.g. $db_url = 'mysql://newdatabaseusername:newdatabasepassword@localhost/newdatabasename'; and save. 13. now you have a database for your new subdomain which you access using 'settings.php' in your new subdomain folder. 14. go to 'sub1.yourlovelysite.com/install.php' and follow the familiar steps for a Drupal installation. That should do it. You can repeat this method over and over: sub2, sub3... and so on.


Important: When you upload files/images to your subdomain, by default, Drupal will place that file in 'sites/default/files'. To organise and to avoid your subdomains overwriting each others' files, change the 'file system path' in 'Administer > Site configuration > File system' to something like: 'sites/sub1.yourlovelysite.com/files'. This should be remembered/reversed if the subdomain is ever moved to its own site, otherwise your pictures will disappear as Drupal is looking in the wrong place for them.

Multiple domains or vhosts using different databases Apache supports both IP- and name-based virtual hosts (vhosts). While running more than one engine (by using vhosts) can be very useful for development and testing purpose, it is most useful for hosting companies. Therefore, we support vhosts in the best possible way in order to make the life of all administrators easier. We do so by making it possible to run an unlimited number of vhosts on the same physical source tree, through using different configuration files. Moreover, you can setup multiple configuration files in your includes-directory. $ ls -l sites/*/*.php -rw-rw-r-1 drupal -rw-rw-r-1 drupal

drupal drupal

sites/www.example1.com/settings.php sites/www.example2.com/settings.php

The only thing left to do is to set up the corresponding vhosts in your Apache configuration file. Note that the DocumentRoot points to the same source tree twice: NameVirtualHost 127.0.0.1 DocumentRoot /home/www/drupal ServerName www.example1.com DocumentRoot /home/www/drupal ServerName www.example2.com

Remember that, as of Drupal 4.6, you can have site specific modules and themes as well. Just create a directory under the sites/www.example1.com called 'modules' and place the site specific modules in it. The same applies to themes as well. Consult the INSTALL.txt that came with your Drupal installation for more details. Note that the same effect can be accomplished without further editing httpd.conf if Apache's default virtual server has the Drupal directory as its document root.

Sharing Drupal tables between databases using MySQL5 Views Problem: You'd like to share some data between two different drupal websites without changing any Drupal code or otherwise having to lift much of a finger. But you don't want to share information unless it meets certain requirements (for example, only users labeled "foo" in somedatabase.sometable) For our example, we'll use the users table. Solution: Use MySQL5 Views (http://dev.mysql.com/doc/refman/5.0/en/views.html) Our "master" users table resides in a database called "master". The database of the site that will have restricted access to our masters users table is called "banana". Assumming you're starting with a fresh instance of the Drupal schema in your database "banana", do this: mysql> use banana; mysql> drop table users; mysql> CREATE VIEW users AS mysql> SELECT * mysql> FROM master.users mysql> WHERE uid IN ( mysql> SELECT uid mysql> FROM somedatabase.sometable mysql> WHERE uid = 0 mysql> OR label = 'foo' mysql> )

Drupal will use banana.users just as it would a normal users table. No other modifications are necessary. Now only "foo" users will be included in the users table for your banana website. Note: Drupal has a dependency that is not really documented. Every users table must have an entry that contains uid=0. It's a "stub" entry that Drupal needs to function properly. A workaround for this dependency is to include "user 0" in the results set that defines your view. See, wasn't that easy?

Multiple domains using the same database If you want to host multiple domains (or subdomains) on top of the same database (e.g. http://example.com/ and http://www.example.com/), simply use symbolic links to setup the required configuration files:


$ ln -s sites/example.com sites/www.example.com $ ls -l sites -rw-rw-r-1 drupal lrwxrwxrrx 1 drupal

drupal drupal

sites/example.com sites/www.example.com -> sites/example.com

If your installation isn't in the root folder then you need to specify the path to the Drupal installation. For example if the URL to your installation is http://www.example.com/drupal/ you would use sites/www.example.com.drupal with a settings.php file in it. If you want cookies to be shared between two sites, you will need to set the value of PHP's session.cookie_domain correctly. In the case above, set it to ".example.com". You can do this through Drupal's .htaccess file.

Multisite setup using a single Drupal instance Note The downside of the following technique is that no two host can share the same IP and different SSL certificates for secure login. This opens up vulnerabilities to your drupal installation.

Multi-site/Single Instance This article is intended for administrators who want to set up multiple sites (perhaps for multiple clients) running on a single Drupal installation. The goal is to keep everyone's site separate (different themes, content, enabled modules etc...) while sharing a single Drupal codebase. I won't go into full details on how to install Drupal in a shared hosting environment, there are several other articles you can reference on that topic, although knowing the following is helpful: You will need to setup a new MySql database noting its name. That database should be assigned a user who has full privileges. Make sure you have the username/password for the DB user handy. Under the root/sites/default directory is a file called default.settings.php. This file must be re-named to settings.php and placed on the server and made writable. Certain PHP functions may need to be turned on or off (When running the install script the first time it should provide this information) Since many shared hosts run CPanel you should be able to take advantage of php.ini Quick Config a nice non threatening web interface that is easier to modify compared to adding a php.ini file with custom overrides or doing the same with your .htaccess file. Although not required, I would recommend that your Drupal implimentation be installed on your primary domain so that it is placed in the html root (i.e. /public_html/). I'll explain how this can make your life easier in a bit. We will assume that you have successfully installed and tested your Drupal installation. Lets assume it is named www.myDupalSite.com. Now it's time to create another site by following these steps: Make sure that www.mysecondsite.com is parked on your server. By default (at least in CPanel) it will point to your root html directory which is exactly what we want. Assuming the parked domain has propagated typing in www.mysecondsite.com at this point should show you your drupal implimentation at www.myDupalSite.com Create a new MySQL database to house the second sites content and information. Assign a database user (with full permissions) creating this account if necessary. You may use the same master user account for all Drupal databases if desired. Create a directory for root/sites/mysecondsite.com Copy the settings.php file in your root/sites/default directory to your root/sites/mysecondsite.com directory. Make sure it has the appropriate file permissions Open the file and modify the database (and database user) settings to point to your second web sites database Now you can go to http://www.mysecondsite.com/update.php and run through the implimentation to populate your new Drupal site database. Congratulations you should now have 2 separate websites capable of using different themes and with different content running on a single Drupal instance. Some good things to be aware of regarding multi-site implementations: If it is appropriate for all modules and themes to be shared with all sites then you can disable update checking on all but your primary Drupal implimentation (which should have all modules enabled). In instances where some sites may have exclusive access to themes and/or modules you don't wish to make generally available you may need to enable update checking for those sites. Themes that you wish to be made available to all sites should be placed in /sites/all/themes/theme_name/. A Theme that should be made available to a specific sites (i.e. mysecondsite.com) should be placed in root/sites/mysecondsite.com/themes/theme_name/ Availability of modules is similar to themes. Utilize root/sites/all/modules when you want a module to be available to all Drupal instances (each instance can choose to enable or disable the module).


Although you are running a single instance of Drupal you have multiple databases that may need to be updated when a new version comes out. Please make sure to disable your sites and run the update.php file once for each site before re-enabling sites when finished.

Same codebase, completely different content and users This document assumes the following: You want to create more than one Drupal site using the same codebase (the same basic set of Drupal files, uploaded into one location). You know how to install mysql tables in your database. You know how to set up subdomains/folders/other domains that you want to use. You have already downloaded Drupal 4.7, and set up one MAIN site. This document contains instructions on how to create completely separate Drupal site, with different configurations, users and content. Nothing is shared between the installations except the codebase. Some definitions: codebase: the basic set of Drupal files, included in the .tar.gz file. the stuff that makes Drupal work. database: a collection of tables that stores data. Affected files: settings.php (sites/default/...) database.mysql For this example, we will assume the following: www.example.com: the MAIN site, where the codebase has already been uploaded, and settings.php has been properly configured. sub1.example.com; www.example.com/sub2; www.subexample.com : the other sites we will create. 1. Create the following folders in your sites directory: sites/sub1.example.com/ sites/www.example.com.sub2/ sites/www.sub3example.com/ Copy the settings.php file from sites/default into each of the above folders. 2. There are different database files (inside the database folder). Select the one that is appropriate for your database version. Copy it to each of the folders you made in step 1. This is OPTIONAL, but it will help you keep things straight. 3. Decide on SEPARATE PREFIXES for each site. Below is what we will use for the example: site

database prefix

sub1.example.com

sub1_

www.example.com,sub2

sub2_

www.sub3example.com

sub3_

4. Configure your settings.php for each site. This code MUST BE CHANGED FOR EACH SITE: $db_url = 'mysql://username:password@localhost/databasename';

Note; if you are using the same database as the MAIN site, the $db_url is the same. OPTIONAL: # $base_url = 'http://www.example.com'; // NO trailing slash! # # # # #

$conf = array( 'site_name' => 'My Drupal site', 'theme_default' => 'pushbutton', 'anonymous' => 'Visitor' );


For sub1.example.com, the settings would be: $db_prefix = 'sub1_'; $base_url = 'http://sub1.example.com'; $conf = array( 'site_name' => 'My SUB1 Drupal Site', 'theme_default' => 'pushbutton', 'anonymous' => 'Visitor' );

For www.example.com/sub2, the settings would be: $db_prefix = 'sub2_'; $base_url = 'http://www.example.com/sub2'; $conf = array( 'site_name' => 'My SUB2 Drupal Site', 'theme_default' => 'fancy', 'anonymous' => 'Anonymous' );

For www.sub3example.com, the settings would be: $db_prefix = 'sub3_'; $base_url = 'http://www.sub3example.com'; $conf = array( 'site_name' => 'My SUB3 Drupal Site', 'theme_default' => 'marvin', 'anonymous' => 'Guests' );

6. Upload the modified settings.php files in their respective folders. 7. Open the database file you copied into each sites folder. Find the following (don't forget the SPACE at the end!!!): CREATE TABLE INSERT INTO

Replace each 'create table' and 'insert into' with 'create table dbprefix_' and 'insert into dbprefix_' For sub1.example.com: CREATE TABLE >> CREATE TABLE sub1_ INSERT INTO >> INSERT INTO sub1_

8. Upload the modified mysql files into the database you specified in your settings.php. 9. Voila!

Setting up multi-site Drupal 5 on Windows or Linux - consolidated instructions If you are new to Drupal and Apache, these instructions will walk you through most of the things you need to do, to get Drupal up and running on Linux or Windows. I haven't covered the details of setting up a database in MySQL with phpMyAdmin, but I do show you how to configure Apache so that this works. For the Linux instructions the file paths are typical for SuSE Linux 10.1 - for most other Linuxes the htdocs path is likely to start at a different place, but the principles are the same. We will assume you want to make a local installation on a Windows PC and an installation reachable via the Internet on a Linux server.

Hosts file You will find the hosts file at the following locations: - on Windows: c:\windows\system32\drivers\etc\hosts - on Linux: /etc/hosts

In these instructions, we will assume you want to install a number of sites at: On Windows (local): On Linux (Internet):

strawberry.loc and blueberry.site yourdomain.com and blueberry.com

The hosts file will look as follows for the Windows example: 127.0.0.1 127.0.0.1 127.0.0.1

localhost strawberry.loc blueberry.site blueberry

For a Linux internet server the hosts file should look as follows:


ipaddress 127.0.0.1

yourhostname.yourdomain.com localhost

yourhostname

Please note that for the Linux server on the Internet you only need to have the Fully Qualified Domainname (FQD) in the form of yourhostname.yourdomain.com in the hosts file. On a Linux system working with DNS, you DO NOT need to make any entries other than the primary FQD in the hosts file. If you want to have a local site at blueberry.site on Linux, you should add the following line to the Linux hosts file: 127.0.0.1

blueberry.site blueberry

Of course blueberry.site would not be reachable through the Internet, because the Internet DNS system would know nothing about it, whereas it would work for a local user on that computer because the local system would know how to deal with it by looking up the information in the computer's hosts file.

DNS Set up the DNS entries for every domainname your server is hosting. Each one of these domains needs to be resolved to your server's ip address. You will normally be able to configure this with your domain registrar or hosting provider. For a local Windows or Linux installation, e.g. for development, no DNS configuration is necessary - the entries in the hosts file on your computer are sufficient. Please note that the default localhosts entry in the hosts file is not sufficient to get multi sites working on a Drupal installation.

Apache Configure Apache for all the different domain names that your web server will be serving. For Linux (our examples use paths for SuSE Linux > 10.0) the best place to do this is in the vhosts.conf file in the /etc/apache2/vhosts.d directory (you can call this file {anything}.conf). Actually every filename ending in .conf in the vhosts.d directory will be picked up, so beware of multiple overlapping entries - they will confuse the web server. The entries can also be placed in the /etc/apache2/httpd.conf file initially to keep things simple. Later it is better to do it in the vhosts.conf file - this way, in case of an update of the Apache software, if the httpd.conf file is overwritten, your configuration will survive. For Windows, you can get Apache, MySQL and PHP from: http://www.apachefriends.org/en/xampp-windows.html

If you installed XAMPP in the default directory on Windows, the vhosts file will be at: c:\program files\xampp\apache\conf\extra\httpd-vhosts.conf

The entries can also be placed in the c:\program files\xampp\apache\conf\httpd.conf

file initially to keep things simple.

On Linux (the paths in the example are for SuSE Linux), your vhosts.conf file (or entries at the end of the httpd.conf file) should look as follows: # # First entry of this file should be the NameVirtualHost entry, unless it is already there # If you want your web server to respond on a different port from the default # replace 80 with that port number # Available alternative port numbers are 8080 and 2222 # NameVirtualHost *:80 # For each domain you want to serve up with Drupal, # you need an entry that will match the incoming domain # You do not need a separate entry for each subdomain - Drupal takes care of that # The following entry takes care of : # yourdomain.com # www.yourdomain.com # bla.yourdomain.com # gaga.yourdomain.com # etc. # # In the following we assume that the Apache htdocs directory is at /srv/www/htdocs # and that Drupal has been installed in /srv/www/htdocs/drupal # <VirtualHost *:80> DocumentRoot /srv/www/htdocs/drupal ServerName yourdomain.com ServerAlias *.yourdomain.com <Directory /srv/www/htdocs/drupal> Allow from all Options +Includes +Indexes +FollowSymLinks AllowOverride all </Directory> </VirtualHost> # # # # # # #

The following entry takes care of : blueberry.com www.blueberry.com bla.blueberry.com gaga.blueberry.com etc.


# # In the following we assume that the Apache htdocs directory is at /srv/www/htdocs # and that Drupal has been installed in /srv/www/htdocs/drupal # <VirtualHost *:80> DocumentRoot /srv/www/htdocs/drupal ServerName blueberry.com ServerAlias *.blueberry.com <Directory /srv/www/htdocs/drupal> Allow from all Options +Includes +Indexes +FollowSymLinks AllowOverride all </Directory> </VirtualHost> # # On Linux, you will also need an entry to ensure that your phpMyAdmin works # - you will need phpMyAdmin to set up and manage your MySQL databases # I assume here that you have unpacked and copied the phpMyAdmin files to # /srv/www/htdocs/phpMyAdmin # Please note that in the following, the DocumentRoot and Directory command # both need to use phpMyAdmin # using phpmyadmin doesn't work even though phpmyadmin # is a symbolic link to phpMyAdmin # <VirtualHost *:80> ServerName phpmyadmin.yourdomain.com DocumentRoot /srv/www/htdocs/phpMyAdmin <Directory /srv/www/htdocs/phpMyAdmin> allow from all Options +Indexes +Includes +FollowSymLinks AllowOverride FileInfo Options </Directory> </VirtualHost>

For the local Windows example, your httpd-vhosts.conf file (or entries at the end of the httpd.conf file) should look as follows: # # First entry of this file should be the NameVirtualHost entry, unless it is already there # If you want your web server to respond on a different port from the default # replace 80 with that port number # Available alternative port numbers are 8080 and 2222 # NameVirtualHost *:80 # For each domain you want to serve up with Drupal, # you need an entry that will match the incoming domain # You do not need a separate entry for each subdomain - Drupal takes care of that # # The following entry takes care of e.g: # strawberry.loc # www.strawberry.loc # bla.strawberry.loc # gaga.strawberry.loc # etc. # # In the following we assume that the Apache htdocs directory is at # c:/program files/xampp/htdocs # and that Drupal has been installed in c:/program files/xampp/htdocs/drupal # <VirtualHost *:80> DocumentRoot "c:/program files/xampp/htdocs/drupal" ServerName strawberry.loc ServerAlias *.yourdomain.com <Directory "c:/program files/xampp/htdocs/drupal"> Allow from all Options +Includes +Indexes +FollowSymLinks AllowOverride all </Directory> </VirtualHost> # # # # # # # # # # #

The following entry takes care of e.g: blueberry.site www.blueberry.site bla.blueberry.site gaga.blueberry.site etc. In the following we assume that the Apache htdocs directory is at c:/program files/xampp/htdocs and that Drupal has been installed in c:/program files/xampp/htdocs/drupal

<VirtualHost *:80> DocumentRoot "c:/program files/xampp/htdocs/drupal" ServerName blueberry.site ServerAlias *.blueberry.site <Directory "c:/program files/xampp/htdocs/drupal"> Allow from all Options +Includes +Indexes +FollowSymLinks AllowOverride all </Directory> </VirtualHost>


It seems that the first entry in vhosts.conf becomes the default Apache server, whereas the last entry in httpd.conf becomes the default. On Windows, you get to phpMyAdmin by pointing your browser to http://localhost/phpmyadmin, if it is installed in the default location under the xampp directory.

Drupal Make sure that the settings.php file in /srv/www/htdocs/drupal/sites/default on Linux or c:\program files\xampp\htdocs\drupal\sites\default on Windows

is the default file that came with the Drupal distribution. Now point your browser to http://yourdomain.com and you will see the Drupal install screen which will walk you through the installation for the site http://yourdomain.com. It will ask you for - the name of the MySQL database - the database user and password, and - ask you to set up the first user who will become the administrator of the site. It is IMPORTANT that the you use this original settings.php file, OR ELSE the Drupal install procedure DOES NOT WORK when you go to http://yourdomain.com. The reason for this is that if you use an existing installations settings.php file, Drupal thinks the site is already configured and DOES NOT start the one time start up installation. Do the same for any other domains you intend to use and have configured in Apache.

Independent Site at a subdomain To have http://bla.yourdomain.com (Linux example) or http://bla.strawberry.loc (Windows local) be a different Drupal site, create a directory called: /srv/www/htdocs/drupal/sites/bla.yourdomain.com on Linux or c:\program files\xampp\htdocs\drupal\sites\bla.stawberry.loc on Windows

and copy the default settings.php file that came with the Drupal distribution into the above directory. It is IMPORTANT that the you use this original settings.php file, OR ELSE the easy Drupal install procedure DOES NOT WORK. Make sure you have created a separate database for this site in MySQL before starting the install. Just point the browser to: http://bla.yourdomain.com on Linux hosting server or http://bla.strawberry.loc on your Windows server

to start the installation. Again, It will ask you: - for the name of the MySQL database - for the database user and password, and - to set up the first user who will become the administrator of the site. Repeat the above for any other sub-domains you want to configure such as http://hello.blueberry.site on Windows.

Independent site at a sub URL To set up a separate Drupal site at http://www.yourdomain.com/gugu create a directory called /srv/www/htdocs/drupal/sites/www.yourdomain.com.gugu c:\program files\xampp\htdocs\drupal\sites\www.stawberry.loc.gugu on Windows

and copy the default settings.php file that came with the Drupal distribution into the above directory. It is IMPORTANT that the you use this original settings.php file, OR ELSE the Drupal install procedure DOES NOT WORK. Make sure you have created a separate database for this site in MySQL before starting the install. Just point the browser to: http://www.yourdomain.com/gugu on Linux or http://www.strawberry.com/gugu on Windows

to start the installation. You will be asked: - for the name of the MySQL database - for the database user and password, and - to set up the first user who will become the administrator of the site.

Mechanism for setting up sub-domain and sub-directory/sub-URL sites For details on how Drupal uses the sites directory structure, read the INSTALL.txt file in the top level drupal directory. Here is the relevant excerpt:

MULTISITE CONFIGURATION A single Drupal installation can host several Drupal-powered sites, each with


its own individual configuration. Additional site configurations are created in subdirectories within the 'sites' directory. Each subdirectory must have a 'settings.php' file which specifies the configuration settings. The easiest way to create additional sites is to copy the 'default' directory and modify the 'settings.php' file as appropriate. The new directory name is constructed from the site's URL. The configuration for www.example.com could be in 'sites/example.com/settings.php' (note that 'www.' should be omitted if users can access your site at http://example.com/). Sites do not have to have a different domain. You can also use subdomains and subdirectories for Drupal sites. For example, example.com, sub.example.com, and sub.example.com/site3 can all be defined as independent Drupal sites. The setup for a configuration such as this would look like the following: sites/default/settings.php sites/example.com/settings.php sites/sub.example.com/settings.php sites/sub.example.com.site3/settings.php When searching for a site configuration (for example www.sub.example.com/site3), Drupal will search for configuration files in the following order, using the first configuration it finds: sites/www.sub.example.com.site3/settings.php sites/sub.example.com.site3/settings.php sites/example.com.site3/settings.php sites/www.sub.example.com/settings.php sites/sub.example.com/settings.php sites/example.com/settings.php sites/default/settings.php If you are installing on a non-standard port, the port number is treated as the deepest subdomain. For example: http://www.example.com:8080/ could be loaded from sites/8080.www.example.com/. The port number will be removed according to the pattern above if no port-specific configuration is found, just like a real subdomain. Each site configuration can have its own site-specific modules and themes in addition to those installed in the standard 'modules'and 'themes' directories. To use site-specific modules or themes, simply create a 'modules' or 'themes' directory within the site configuration directory. For example, if sub.example.com has a custom theme and a custom module that should not be accessible to other sites, the setup would look like this: sites/sub.example.com/: settings.php themes/custom_theme modules/custom_module NOTE: for more information about multiple virtual hosts or the configuration settings, consult the Drupal handbook at drupal.org.

Permissions FINALLY, make sure all the directories from /srv/www/htdocs/drupal downwards are accessible by the web server. On SuSE Linux, execute: chown -R wwwrun:www /srv/www/htdocs/drupal

to ensure that the directories have the right permissions. The userid (uid) is wwrun and the group id (gid) is www. These can also be configured somewhere in Apache. For other Linux distributions, find out as what user and group Apache runs as and where the htdocs Apache web server root directory is. Nothing further can stand in the way of your Drupal development - enjoy.

Setup of /sites directory for multi-site Drupal's multi-site hosting capability is built in with any installation. This is great news for users who run numerous web sites from a single hosting account. A single Drupal installation can be used to run multiple domains, which makes it much easier to manage and maintain the code base. Even if you are dealing with only one domain, the multi-site capability may be valuable by providing the ability to run a separate domain or sub-domain for a development version. This page describes the set-up of the /sites directory for multi-sites. With version 5.x, the intended location for all non-core elements of a Drupal installation is in a separate /sites directory inside the Drupal installation. Directory /drupal/sites/all

Contents


/drupal/sites/all (used by all sites) /drupal/sites/default (used when there is no /sites/example.com directory)

/modules /themes /files settings.php

/drupal/sites/example1.com

/files /modules /themes /tmp settings.php

/drupal/sites/example2.com

/files /modules /themes /tmp settings.php

The intended best practice configuration is to create a /sites/example.com directory for each domain. It should contain a site-specific settings.php file and /files directory. Configure Drupal site settings to specify 'File System Directory' of 'sites/example.com/files' instead of the default 'files'. It's possible to do this with an existing web site, but moving file uploads around can cause a lot of confusion if there are already URLs pointing to the old locations. Domain specific modules and themes should also be placed in /sites/example.com/modules and /sites/example.com/themes respectively. Contributed modules and additional themes which are for use by all domains in a multi-site installation should be placed in /sites/all/modules and /sites/all/themes. Note that there shouldn't be a /sites/all/files or /sites/all/settings.php. The /sites/default directory should contain /files and settings.php, for use if the /sites/example.com directory doesn't exist for a domain. In addition to multiple sites, such as example1.com and example2.com, sub domains are also easily set up. Adding sub3.example2.com and sub3.example2.com/site4, the directory structure for these four sites would be: /drupal/sites/all/modules /drupal/sites/all/themes /drupal/sites/default/files /drupal/sites/default/settings.php /drupal/sites/example1.com/files /drupal/sites/example1.com/modules /drupal/sites/example1.com/settings.php /drupal/sites/example1.com/themes /drupal/sites/example1.com/tmp /drupal/sites/example2.com/files /drupal/sites/example2.com/modules /drupal/sites/example2.com/themes /drupal/sites/example2.com/tmp /drupal/sites/example2.com/settings.php /drupal/sites/sub3.example2.com/files /drupal/sites/sub3.example2.com/modules /drupal/sites/sub3.example2.com/settings.php /drupal/sites/sub3.example2.com/themes /drupal/sites/sub3.example2.com/tmp /drupal/sites/sub3.example2.com.site4/files /drupal/sites/sub3.example2.com.site4/modules /drupal/sites/sub3.example2.com.site4/settings.php /drupal/sites/sub3.example2.com.site4/themes /drupal/sites/sub3.example2.com.site4/tmp

Note that Drupal reconizes www.example.com as a sub-domain of example.com. If you wish to point both of them to the same site, use /drupal/sites/example.com/ as your directory, and uncomment the corresponding option in your .htaccess Once you've done this, the file structure of your site will be cleanly organized: The main Drupal directory will contain only the standard 'core' files. Themes and modules that are shared among all sites are properly placed in /sites/all Site-specific themes, modules, and files are compartmentalized and properly placed in /sites/example.com, /sites/example1.com, /sites/sub3.example2.com and /sites/sub3.example2.com.site4 . /sites/default/settings.php and /sites/default/files will be used if /sites/example.com directory does not exist. Backing up the /sites directory and your Drupal database will give you everything you need to restore the site in the event of a crash, or to move to a new server. Adding a domain is easy: just copy the /sites/default directory to /sites/example5.com To help keep files organized you may choose to use shortcuts to point relevant files and directories that are stored elsewhere in your Drupal installation. These short-cuts (like a desktop "alias") are referred to as "symbolic links" on a Web server. Symbolic links can be used for several purposes: Even if using default settings, a good option is to use links from /sites/example.com directory to point to the /sites/default directory. That way, if the settings and /files are ever changed from the default and actually placed in /sites/example.com, their location does not 'move' and no links are broken.


Links could also be used to point the /sites/default directory to your primary site. A /files directory could easily be shared across two domains without being shared across the remaining domains. A non-domain-name path for /files can be setup. If it is possible that the domain name might change (say, from a development name), then you can set up a link from /drupal/sites/moniker to /drupal/sites/example.com, where 'moniker' is a short version of the site name that will remain constant even if /example.com changes. If you are working from the command line on a Linux, Unix or OSX server you can create a symbolic link using the following command: $ ln -s /path/to/actual/file/or/directory name_of_shortcut

Although the /sites/default directory could contain a /modules and /themes directory, these elements should usually be placed in /sites/all or /sites/example.com. Similarly, although contributed modules could be placed in /drupal/modules as was the practice in version 4.7, this is not recommended. Multi-site directory setup for sub-domains, including non-standard ports, is described in the installation instructions found in INSTALL.txt. See multidomain for a contributed module that allows spanning one site across multiple domains, so that specific content types appear on specific domains or sub-domains. Version 4.6 and 4.7: Best practice for multi-site set-up under version 4.6 and 4.7 is similar to 5.x. The primary difference is that there is no /sites/all directory. Instead, /modules and /themes that are available for all domains are kept in /drupal/modules and /drupal/themes. Files Directory The following user-submitted code may be useful in redirecting URLs for the /files directory to the /sites/example.com/files directory. The following code is added to the [drupal_root]/files/.htaccess file: RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ /sites/%{HTTP_HOST}/files/$1 [L]

If the .htaccess method doesn't work, try this user-contributed code for use in settings.php, instead of .htaccess. In settings.php, put this code: <?php $conf['file_directory_temp'] = 'sites/' .substr(strrchr(dirname(__FILE__), '/'), 1) .'tmp'; $conf['file_directory_path'] = 'sites/' .substr(strrchr(dirname(__FILE__), '/'), 1) .'/files'; ?>

Video about Multisite optimization Here is a video I'm contributing about configuring a Multisite Drupal install within an Apache server environment. It provides a number of tips related to optimizing the httpd.conf file for better security on files such as install.php, cron.php and update.php. It also provides tips on managing the multisite configuration by using a dedicated drupal.conf file and controlling access to sensitive areas of the site (like /admin) using an ip.conf file. One other valuable tip is temporarily using the apache directive DirectoryIndex (while updating drupal) to point all sites managed in the multisite install to a different index page (such as offline.php) so you don't have to go into every site and turn on maintenance mode (which can be a pain).

Share a single database across multiple sites Some web hosts limit their customers to one database. Thus, no duplicate table names are possible. In order to assure that these admins can still use Drupal, and even use multiple installations of Drupal, Drupal offers table prefixing. In order to use this feature, you must currently edit the script database/database.x in order to create tables prefixed by the string of your choice. If you are adding contributed modules you will need to also modify INSERT and REPLACE statements to have a prefix as well. For example, change all statements from the format of CREATE TABLE access INSERT INTO system VALUES ('modules/filter.module','filter','module','',1,0,0);

Becomes, CREATE TABLE dr1_access INSERT INTO dr1_system VALUES ('modules/filter.module','filter','module','',1,0,0);

Then use dr1_ (for example) as value of $db_prefix in your sites/example.com/settings.php


file. You can also use the prefix.sh in the scripts subdirectory of your Drupal install to do this automatically. You can then update several sites in one swoop with a (bash shell) command-line like for F in '' prefix1 prefix2; do \ for S in `find ./modules --name \*mysql`; do \ scripts/dbprefix.sh $F < $S | grep -v DROP |\ mysql -h DBHOST -u DBUSER -pPASSWD DATABASE; \ done; done

NOTE:that not all scripts end in mysql, but you get the idea; the '' prefix tells the script to run with no prefix at all, ie a straight cat - command that just lets me process all my sites together. Here is a PHP script that creates all of the database tables for all sites in /drupal/sites/*. It works for DRUPAL-4-6. See the Database Table Creation script issue for more info. If you want to share users across multiple sites, you'll need to use a $db_prefix along these lines: <?php $db_prefix = array( 'default' => 'authmap' => 'profile_fields' => 'profile_values' => 'role' => 'sequences' => 'sessions' => 'users' => 'users_roles' => 'users_uid_seq' => ); ?>

'thissite_', 'shared_', 'shared_', 'shared_', 'shared_', 'shared_', 'shared_', 'shared_', 'shared_', 'shared_', // for pgsql

See the sharing tables and sharing tables across databases sections.

Share tables across instances (not recommended) Please note: This procedure could result in unexpected results, depending on which tables you choose to share, including broken version updates and/or security holes. For instance, if one site is compromised, an attacker could compromise the shared database tables and compromise your other sites. Think about both the settings to be shared and be aware of the risks involved. You can, however, host multiple sites in the same database (with no shared tables) by using site-wide table prefixes. By using table prefixes on some tables but not others, you can cause multiple Drupal installations to share a set of common tables. One interesting application for this is to share the taxonomy tables (vocabularies, term_data). Another interesting use is to share users across Drupal installations. In order to use this capability, create two drupal installs in same DB using different database prefixes. In this example, one is prefixed 'master_' and the other 'slave1_'. Then edit the settings.php file of 'slave1_' so that it points some tables to the 'master_'. For sharing users, add the following: $db_prefix = array( "default" => "slave1_", // the prefix for tables that are not shared. "users" => "master_", "sessions" => "master_", "authmap" => "master_", "sequences" => "master_", "profile_fields" => "master_", "profile_values" => "master_", );

Note: The actual tables that you will share depends on your installation. However, the following tables contain data that is highly site specific and therefore should not be shared: cache variable There is a limitation in that you can only explicitly specify which tables will be shared and not the other way round. The following may fail (however a recently deleted comment on this page by marcob suggests this has been fixed: $db_prefix = array( // Be careful of this setup. 'default' => 'primary_', 'cache' => 'slave1_', 'node' => 'slave1_', 'system' => 'slave1_', // etc... );


Setup tip for Drupal 5 For Drupal 5+, an easy way to get started with table sharing across instances is to run the installer twice using the same database - it will create the prefixed tables and handle all the initial INSERTS (as with the 'system' and 'menu' tables, etc) for all your different prefixes.

Using schema prefixes with PostgreSQL This page discusses usage of PostgreSQL schemas for prefixes. "Normal" prefixes can be used in the same way as in MySQL, so they won't be discussed here. PostgreSQL has something called 'schemas' (http://www.postgresql.org/docs/current/static/ddlschemas.html). They can be very handy sometimes, but if you don't know what they are, you probably don't actually need them and can stop reading here. Schemas can be used as prefixes within Drupal. That is, with a multisite setup, each site can reside in its own schema, and shared tables can reside in a "shared" schema (or even in the public schema). There is one annoyance: the upgrade will fail. This is unfortunate, but nothing can be done as the "normal" (not schema) and schema prefixes are just incompatibile. If you are interested in the details, please see http://drupal.org/node/40034. But, don't worry. This can be easily fixed by changing the update script (update.php and updates.inc) a bit. The problem lies in the CREATE [UNIQUE] INDEX and ALTER TABLE ... DROP/ADD CONSTRAINT statesments. When schema prefixes are used, queries like this are executed: CREATE INDEX prefix.search_total_word_idx ON prefix.search_total(word) ALTER TABLE prefix.boxes DROP CONSTRAINT prefix.boxes_title_key ALTER TABLE test.contact ADD CONSTRAINT test.contact_category_key UNIQUE (category)

The prefix must be removed from the index and constraint name--that is they must be changed to: CREATE INDEX search_total_word_idx ON prefix.search_total(word) ALTER TABLE prefix.boxes DROP CONSTRAINT boxes_title_key ALTER TABLE test.contact ADD CONSTRAINT contact_category_key UNIQUE (category)

You can easily search for CREATE INDEX, CREATE UNIQUE INDEX and ADD/DROP CONSTRAINT statements and remove the {} from index/constraint names. The best way is to run a test upgrade. You'll see a list of failed queries and it will be easier for you to change them. Another remark: you can't use prefix.sh to prefix the tables, it will produce incorrect CREATE [UNIQUE] INDEX queries. This, also, can be easily fixed, by changing: s/^CREATE INDEX \(.*\) ON /CREATE INDEX $PREFIX\\1 ON $PREFIX/; s/^CREATE UNIQUE INDEX \(.*\) ON /CREATE UNIQUE INDEX $PREFIX\\1 ON $PREFIX/;

to: s/^CREATE INDEX \(.*\) ON /CREATE INDEX \\1 ON $PREFIX/; s/^CREATE UNIQUE INDEX \(.*\) ON /CREATE UNIQUE INDEX \\1 ON $PREFIX/;

An alternate approach Here's what you need to do if you want to solve it this way: 1. Add the following at the top of your database.pgsql file: CREATE SCHEMA schemaname; SET search_path TO schemaname;

2. Edit drupal/includes/database.pgsql.inc, replacing the function db_connect() with: <?php function db_connect($url) { $url = parse_url($url); $db_and_schema = explode(".",substr($url['path'], 1)); $conn_string = ' user='. $url['user'] .' dbname='. $db_and_schema['0'] .' password='. $url['pass'] . ' host=' . strtr($url['host'],'+','/'); $conn_string .= isset($url['port']) ? ' port=' . $url['port'] : ''; $connection = pg_connect($conn_string) or die(pg_last_error()); if(!empty($db_and_schema['1'])) pg_query('SET search_path TO '.$db_and_schema['1']); return $connection; } ?>

3. Finally, use a db_url akin to such in your settings.php file(s): $db_url = 'pgsql://user:password@+tmp/dbname.schemaname';


Not thoroughly tested, but works for me (on 4.6.3). This also fixes the inability to specify a Unix socket as the host - the +tmp gets replaced with /tmp for pg_connect().

Drupal 6.x SQL command after database connect Actually, as of Drupal 6.9, I couldn't find a file named updates.inc, and a grep 'CONSTRAINT' update.php modules/update/* returned nothing. In the second solution, the trick is to * embed the schema name in $dburl (in settings.php), * then modify db_connect() code to o extract the schema name from $dburl o rebuild a suitable connection string (remove it from $db_url) for pg_connect() o build and send a SQL instruction to the engine, telling it to use the specified schema.

Searching db_connect() in Drupal site, I found this old post #26549: SQL command after database connect ", unfortunately closed. The links it points to restrict the idea to fixing encoding (now rigidly set to utf-8). It sounds to me like a cleaner and more flexible version of the second approach described here. I posted this alternate solution as #375763: Running site-specific SQL commands on database connect

Moving Drupal tables to their dedicated PostgreSQL schema After applying the patch I submitted in #375763: Running site-specific SQL commands on database connect, I created a blog entry, with PHP code input format, and the following body content: <?php global $db_type; if ($db_type!='pgsql') die ("This tool is intended for PostgreSQL databases only. Current engine: $db_type"); $res = db_query("SELECT current_schema();" ); $pg_schema_active = db_result($res); $pg_schema_new = 'drupal'; if ($pg_schema_active == $pg_schema_new) { echo "<p>Current schema is '$pg_schema_new' already.\n"; echo "The following is useless.</p>\n"; } echo <<< EOT <p>To move your Drupal tables from current PostgreSQL schema "<code>$pg_schema_active;</code>" to their dedicated "<code>$pg_schema_new;</code>" namespace, you need to: </p> <ol> <li>save this code in a file, e.g. "<code>migrate-drupal-schema.sql</code>"</li> <li>put your Drupal site offline (maintenance mode).</li> <li>Enable PostgreSQL schema support in your Drupal site (Issue [#375763])</li> <li>execute sql code with 'psql' or 'phppgadmin' for example</li> <li>edit your <code>settings.php</code> file and add (a good place is just below \$db_url and \$db_prefix): <pre> \$db_after_connect_sql = "SET search path TO $pg_schema_new;"; <pre> </li> <li>Put your Drupal site back online.</li> </ol> EOT; $drupal_schema = drupal_get_schema(NULL, TRUE); $migrate_sql = "CREATE schema $pg_schema_new;\n\n"; foreach (array_keys($drupal_schema) as $table_name) { $migrate_sql .= "ALTER TABLE $pg_schema_active.$table_name SET SCHEMA $pg_schema_new;\n"; } echo "<textarea cols=\"80\" rows=\"15\" name=\"migrate-drupal-schema\">\n"; echo $migrate_sql; echo "</textarea>\n"; ?>

Its output should be self explanatory. Please post comments in the support forum if you have problems.

Define shared variables for all sites When you create a multi-site installation, an important table to duplicate is the variable table. However, by duplicating this table, you will also duplicate some variables that you might prefer


not to. To force a set value for these variables, for all of your websites, you can do the following. First, you will probably have a settings.php file for each site in your installation, like sites/example.com/settings.php. Edit each file and add the following to the end. include_once ('./sites/default/shared_variables.php');

Now, in the default directory, add a file called sites/default/shared_variables.php containing the following. <?php /** * These variables are fixed for all sites that have this line of * code in their settings.php file: * * include_once ('./sites/default/shared_variables.php'); */ $conf = array( 'site_name' => 'All these sites are belong to us.', 'theme_default' => 'pushbutton', 'anonymous' => 'Visitor' ); ?>

The element names (eg. 'site_name') correspond to a variable in the variable table. So no matter how many copies of the variable table that you need, each site in your multi-site installation will defer to the variables that you define in the $conf array in your shared_variables.php file. The disadvantage is that these variables cannot be edited via the Drupal admin pages.

Clean URLs By default, Drupal uses and generates URLs for your site's pages that look like "http://www.example.com/?q=node/83." This style of URLs can be hard to read, and can prevent some search engines from indexing all the pages of your site. Research suggests this may not be as great an issue for some of the major search engines as it once was; however, it is worth noting the recommendation from Google's webmaster guidelines stating: If you decide to use dynamic pages (i.e., the URL contains a "?" character), be aware that not every search engine spider crawls dynamic pages as well as static pages. It helps to keep the parameters short and the number of them few. If you are unhappy with the default URLs in Drupal, you may be able to tell Drupal to use "clean URLs", eliminating the "?q=" in your site's URLs, and this page explains how to do it. The instructions below are largely applicable only for the most common server setup, which is an Apache web server running on some flavor of Unix/Linux, with the mod_rewrite Apache module configured and mod_rewrite enabled in httpd.conf configuration file. If you are running Drupal on a different type of server, check the links section below (just above the Comments section of this page) to see if there might be something that addresses your server configuration on a different page. Before enabling clean URLs in the Drupal configuration screens (see below), you may need to prepare your server for clean URLs to work. There are two ways to prepare your server for clean URLs to work in Drupal. If you have complete control of your server, for example because you run your own server, are installing a development site on your personal computer, or have a dedicated server hosting account, then you should enable clean URLs in the httpd.conf file for better performance and security. However, if you have a shared hosting account (at DreamHost, BlueHost, HostGator, GoDaddy, 1and1, et al.), you will not be able to modify the httpd.conf file and should use the Drupal .htaccess file instead.

Enabling Clean URLs in Drupal Note: The standard Drupal installation contains a sample .htaccess file which supports clean URLs. It is easy to miss copying this file, because of the leading "dot". So before trying to enable Clean URLs, make sure this file exists in your Drupal installation.

Drupal 6.x In Drupal 6, the installer tests for compatibility with Clean URLs as a part of the installation process. If the installer was not able to run the test successfully at install time, you can later follow the instructions below for Drupal 5 to get Clean URLs working. There is one minor difference: Drupal 6 will run the clean URL test automatically when you navigate to the Clean URLs configuration page and will show the results, in place of giving you a link to run the test manually. Also note that even if Clean URLs are successfully enabled at install-time, if you have a dedicated server you may still want to follow the steps below to enable the more efficient httpd.conf rewrite method for clean URLs. If you choose to do that, you might want to turn off Clean URLs while you are working on the server.

Drupal 5.x Here are the steps necessary to enable Clean URLs in Drupal 5: 1. Goto the administer >> site configuration >> clean urls section of the administrative


interface. 2. Look for the paragraph that reads as follows: This option makes Drupal emit "clean" URLs (i.e. without ?q= in the URL.) Before enabling clean URLs, you must perform a test to determine if your server is properly configured. If you are able to see this page again after clicking the "Run the clean URL test" link, the test has succeeded and the radio buttons above will be available. If instead you are directed to a "Page not found" error, you will need to change the configuration of your server. The handbook page on Clean URLs has additional troubleshooting information. Run the clean URL test. 3. Click on the Run the clean URL test link at the end of the above paragraph. 4. If the test is successful, set Clean URLs to "enabled" and save the configuration. If the test is not successful, use the steps below to fix your server configuration and try again.

Prior to Drupal 5.x For Drupal versions prior to Drupal 5, there is no automatic Clean URLs test or link. Instead, you can test manually by typing in the Clean URL for your settings page: http://www.example.com/admin/settings (where www.example.com is replaced by your hostname). If this results in seeing the settings page, and no errors, then Clean URLs are safe to enable, and you can do so with the setting on this page. If there is an error, follow the instructions below to configure your server.

Error recovery Enabling "Clean URLs" when your server is not properly configured (i.e. if the Clean URLs tests described above fail) can make it difficult to navigate back to administration pages to undo your mistake, because all the Drupal-generated menus and links will have URLs that do not work. If you find yourself in this situation, you can return to the administrative settings page by typing in the URL in the 'non-clean' form: http://www.example.com/?q=admin/settings for the admin settings page in Drupal 4.x, or http://www.example.com/?q=admin/settings/clean-urls to get to the Drupal 5 or Drupal 6 Clean URLs settings page. Once there, you should be able to reset to not using Clean URLs. There are additional instructions for recovering from malfunctioning Clean URLs the Handbook page Unset clean URLs.

Server configuration for Clean URLs on a dedicated server, with httpd.conf Enabling clean URLs on a dedicated server involves these steps: 1. Enable mod_rewrite for Apache. You can talk to your web host or consult the Apache documentation for mod_rewrite to get more information on how to do this. At a minimum, this will involve making sure that mod_rewrite is enabled for your installation of Apache. To test if mod_rewrite is available in Apache2, you can type the following at a command prompt, to list all installed Apache modules: apache2ctl -M

On some systems this command may be: apachectl -M

In the output, check to see if the rewrite_module is included in the list of modules. If the rewrite module is not in the list, it will have to be either compiled-in or made available as a loadable module. Generally speaking, you can tell Apache to load the module by including LoadModule rewrite_module modules/mod_rewrite.so AddModule mod_rewrite.c

in your Apache configuration file (see below for information on the configuration file). Be sure to uncomment AddModule mod_rewrite.c, if it is in your configuration file but has been commented out. The following may work to enable the module without editing any files: a2enmod rewrite

Note that these approaches may not work for all combinations of operating system and Apache server -- consult the Apache documentation that came with your Apache software for the correct syntax. Remember to restart Apache for the new configuration to take effect. 2. The next step is to locate the appropriate Apache configuration file for your site. Depending on your server configuration, the appropriate Apache configuration file could be httpd.conf, a virtual-host-specific file (vhost.conf), a specific site file (e.g. "default"), or apache2.conf. They are usually located in /etc/httpd/conf, /etc/apache2, or a sub-directory; if not, try


the command: find /etc -name httpd*

to find the file if it is located elsewhere in your file system. If you do not have write permissions to these files, and Clean URLs are not working out-of-the-box for you, you may have to ask your systems administrator or hosting provider for help. You may still be able to read these configuration files to troubleshoot a little however. 3. The next step is to copy or include the Drupal-specific settings directly into your configuration file. There are instructions here for how to include the Drupal directives in your configuration file. Consult the .htaccess file in Drupal page for examples of rules, such as the following: <Directory /var/www/example.com> RewriteEngine on RewriteBase / RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php?q=$1 [L,QSA] </Directory>

Note: If you do not want to put the rewrite rules in your Apache configuration file, you can still simply use the Drupal .htaccess file (as you would if you were on shared hosting). You will need to have the Allow Override directive set in your Apache configuration file (this will allow local .htaccess overrides on your site): AllowOverride All AccessFileName .htaccess

Read "Behind the scenes with Apache's .htaccess for a thorough review of .htaccess; this other site has samples of Apache 2 directives. Note Regarding MultiViews: Apache supports a feature called "MultiViews" (more generally: "Content Negotiation"), which allows navigation to your pages without the need for file extensions. For instance, if you had a file called "evaluation.txt", a MultiViews-enabled site could access this file with the URL "example.com/evaluation". While MultiViews can be a handy feature when used knowingly, it can cause problems when Drupal's Clean URLs are enabled. Unless you know what you're doing, you should not use MultiViews if you plan to use the Clean URLs feature of Drupal. However, MultiViews is not enabled in a default Apache installation, so it is likely that this note will not apply. Consult the Apache documentation for further information about MultiViews.

Server configuration for Clean URLs on a shared server, with .htaccess The standard Drupal installation contains a sample .htaccess file which should be sufficient to get Clean URLs running. It is easy to miss copying this file, because of the leading "dot". So before trying to enable Clean URLs, make sure this file exists in your Drupal installation. If you have this file installed, but Clean URLs still do not work, you can try some of the troubleshooting suggestions below. If you still cannot get Clean URLs to work, contact your hosting provider.

Fixing problems Check .htaccess is even being used Apache needs to be explicitly told to respect the instructions in your sites .htaccess file. This is off by default, though most hosts will have turned it on. That is what the AllowOverride All directive above does - it makes .htaccess start working. To check if your host is currently even reading your .htaccess, you can (temporarily) add some garbage string to the file in an attempt to break it. Your site should immediately start returning a "500 Server Error" when you load a page from that directory due to this misconfiguration. (Remove the garbage string immediately) If you do this, and your site does not break - then .htaccess is being ignored and you will not be able to use clean URLs until you get support from your hosts. Some hosts allow you to enable this option through their site management control panel, so look there first.

RewriteBase setting The main configuration option which may need to be changed for your site is the RewriteBase. This can be specified in the Drupal .htaccess file or in the httpd.conf file, depending on where you are putting the Drupal rewrite directives (see above). By default, the RewriteBase setting is commented out of the Drupal .htaccess file, and that works well for many configurations. If you are having trouble getting Clean URLs to work, you may need to change this setting. For example, if your Apache DocumentRoot is /var/www/ (i.e., /var/www/index.html is what is displayed when you point your browser at http://www.example.com/) and your Drupal installation is installed in the subdirectory /var/www/mysite/, then the RewriteBase could be set to


RewriteBase /mysite

and that might help. In some configurations, setting RewriteBase /

will allow clean URLs to work.

Multi-site works when your Drupal installation serves only one site, or when all the sites it serves are in the same subdirectory of their domains. For example, RewriteBase

RewriteBase /

will work for the following sites: http://www.example.com/ http://www.example2.com/ http://www.example3.com/ And RewriteBase /mysite

will work for the following sites: http://www.example.com/mysite http://www.example2.com/mysite http://www.example3.com/mysite However, if your sites are in different subdirectories, RewriteBase will not work. You will need to create a special rule for each subdirectory. For example, your Drupal installation may serve the following sites: http://www.example.com/ http://www.example.com/mysite In order to enable clean URLs for both sites, you will need to add RewriteCond RewriteCond RewriteCond RewriteRule

%{REQUEST_FILENAME} !-f %{REQUEST_FILENAME} !-d %{REQUEST_URI} ^/mysite/(.*)$ ^(.*)$ /mysite/index.php?q=$1 [L,QSA]

before the existing rewrite rules.

Location of index.php For some server configurations, another change to the Drupal .htaccess file may be necessary. Find a line that looks like this, near the end of your Drupal .htaccess file: RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]

You may need to replace index.php with the URL path to your Drupal installation's index.php file (only the part after the base URL). For instance, if your site's home page URL is http://example.com/subdir/, you might need to use /subdir/index.php instead of index.php. If your site's home page URL is http://example.com/, you might need to use /index.php instead of index.php. This is necessary on some, but not all server configurations.

Even cleaner URLs with the Path Module Using Clean URLs will cause Drupal to generate URLs in the form "http://www.example.com/node/83." In order to change the 'node/number' portion of the URL to something more like 'news/june-1st-news' a site will need the Path module enabled. See the Path module handbook page for more information on using the path module.

A step-by-step process for enabling clean URLs I am running vmware workstation with Ubuntu 8.10 with LAMP and Drupal 6.6. How I was able to enable clean URLs.

1. Enable the Apache Module Open Terminal and type: apache2ctl -M

to see if rewrite_module is there. If it is not there, first go to the file browser and navigate to /etc/apache2/mods-available


folder and confirm that rewrite.load exists. If it does not exist, then the rest of this won't help until it does. If it does exist, we need to create a symlink from mods-enabled to mods-available. So in Terminal type or copy/paste: cd /etc/apache2/mods-enabled ln -s ../mods-available/rewrite.load

(that first letter is a lowercase L ) If permission is denied, put sudo before ln sudo ln -s ../mods-available/rewrite.load

2. Check the Apache Module Confirm that rewrite.load is now in the mods-enabled folder by opening the folder and finding it, or typing into Terminal apache2ctl -M

So far so good? Great!

3. Enable AllowOverride All Next we need to open sites-enabled in the apache2 folder: /etc/apache2/sites-enabled

and right-click the 000-default file and click the permissions tab. If the access is read-only, open Terminal and type: sudo nautilus /etc

Doing that pops up the file browser as root. Navigate to 000-default again: /etc/apache2/sites-enabled/000-default

Right-click again, go to permissions and change all the read only's to read and write (mine change back automatically after I close the file directory but you might want to double-check.) In this file it will say AllowOverride None in several places, each time inside of a different Directory tag. Find the one for where you've installed Drupal, and change it to AllowOverride All. For instance, if Drupal is installed at /home/username/www/drupal6, then it may be under the Directory "/home/username/www" grouping. Explanation AllowOverride All is an Apache directive that tell it to read and set any of the instructions found in .htaccess files. On some servers this is restricted allowing you to change the error document, but not the directory indexes for example. The .htaccess file that comes with Drupal expects to be able to set a few (reasonably safe) overrides, so we tell Apache to obey (allow) these instructions. After this, open Terminal and and restart apache by typing: sudo /etc/init.d/apache2 restart

You must restart Apache after every change you make to the apache files (if you expect to see changes in Drupal, for example). Last but not least, I COULD VERY WELL BE WRONG AND DOING SOMETHING QUITE AWFUL, but it did work for me. I figured this out after finding these two sites: http://www.jonathansblog.net/node/22 and http://linux.derkeiler.com/Mailing-Lists/Debian/2005-03/2483.html ~Admerin

Configuring clean URLs for various systems This section provides instructions for configuring clean URLs on specific hosts.

Apache 2 configuration of clean URLs on Debian Note: This article needs to be updated for Debian "Etch" and "Lenny" releases. (as of March 2009) If you're running Apache 2 on Debian stable, in order to install the rewrite module you simply need to: # a2enmod rewrite

then restart the webserver: # /etc/init.d/apache2 restart

then edit either /etc/apache2/sites-enabled/drupal or to your .htaccess and ensure it looks something like this:


# # Apache/PHP/site settings: # # Protect files and directories from prying eyes: <Files ~ "(\.(conf|inc|module|pl|sh|sql|theme|engine|xtmpl)|Entries|Repositories|Root|scripts|updates)$"> order deny,allow deny from all </Files> # Set some options Options -Indexes Options +FollowSymLinks # Customized server error messages: ErrorDocument 404 /index.php # Set the default handler to index.php: DirectoryIndex index.php # Overload PHP variables: <IfModule sapi_apache2.c> # If you are using Apache 2, you have to use <IfModule sapi_apache2.c> # instead of <IfModule mod_php4.c>. php_value register_globals 0 php_value track_vars 1 php_value short_open_tag 1 php_value magic_quotes_gpc 0 php_value magic_quotes_runtime 0 php_value magic_quotes_sybase 0 php_value arg_separator.output "&" php_value session.cache_expire 200000 php_value session.gc_maxlifetime 200000 php_value session.cookie_lifetime 2000000 php_value session.auto_start 0 php_value session.save_handler user php_value session.cache_limiter none php_value allow_call_time_pass_reference On </IfModule> # Various rewrite rules <IfModule mod_rewrite.c> RewriteEngine on Options All # Modify the RewriteBase if you are using Drupal in a subdirectory and the # rewrite rules are not working properly: RewriteBase /drupal # Rewrite old-style URLS of the form 'node.php?id=x': RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{QUERY_STRING} ^id=([^&]+)$ RewriteRule node.php index.php?q=node/view/%1 [L] # Rewrite old-style URLs of the form 'module.php?mod=x': RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{QUERY_STRING} ^mod=([^&]+)$ RewriteRule module.php index.php?q=%1 [L] # Rewrite URLs of the form 'index.php?q=x': RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php?q=$1 [L,QSA] </IfModule> # $Id: .htaccess,v 1.58 2004/10/09 20:41:49 dries Exp $

This is because the debian package installs the drupal "sites-enabled" virtual host, result being that drupal is accessed from http://example.com/drupal, hence the need to uncomment RewriteBase. Change it to the base that corresponds to your system. ref: http://www.debian-administration.org/articles/136 http://drupal.org/node/14322 -Sean K. O'Brien CTO Colley Graphics, LLC http://www.colleygraphics.com

Apache 2 on Debian Lenny w/Backports Drupal 6.16 (and Squeeze) These instructions enable you to configure apache2 to support Drupal's Clean URLs for a default lenny backports installation of drupal 6 (currently drupal6 6.16-1~bpo50+1). Therefore, these instructions should also cleanly work for Debian Squeeze (currently in testing). The default installation serves drupal 6 as http://localhost/drupal6/. These instructions may work for earlier versions of Drupal 6 on Lenny and possibly Etch and possibly Ubuntu versions. Please comment if you have success with these other versions.


For information about Debian Backports, visit http://www.backports.org/.

How To To enable Clean URLs in the defalt configuration, first, ensure that the apache2 "Rewrite Module" is enabled: sudo a2enmod rewrite

Next, edit one and only one file, /etc/apache2/conf.d/drupal6.conf, and make these mods within the <Directory> block (careful, this file is a symlink: /etc/apache2/conf.d/drupal6.conf -> /etc/drupal/6/apache.conf): 1. Change AllowOverride to None 2. Add the Rewrite Module directive RewriteBase, setting it to /drupal6 3. Include the default drupal 6 .httaccess file, /usr/share/drupal6/.htaccess. The modified /etc/apache2/conf.d/drupal6.conf file will now look like this (changes in bolditalic): Alias /drupal6 /usr/share/drupal6 <Directory /usr/share/drupal6/> Options +FollowSymLinks # AllowOverride All AllowOverride None order allow,deny allow from all <IfModule mod_rewrite.c> RewriteBase /drupal6 </IfModule> Include /usr/share/drupal6/.htaccess </Directory>

After changing the file, restart apache2 with the command: sudo /etc/init.d/apache2 restart

Then navigate to your drupal adminstrative site configuration Clean URLs page, ensure that the system is correctly configured, and then enable Clean URLs and test.

Discussion Changing AllowOveride to None and including the .htaccess file within the <Directory> means that Apache2 will only load the .htaccess file once (at startup). I recommend against the practice of adding more AllowOveride All directives because this will cause apache2 to read the .htaccess file on every web request, instead of just once on startup. The RewriteBase /drupal6 directive appears as a comment in the /usr/share/drupal6/.htaccess file, but I recommend putting the directive in the conf.d/drupal6.conf file because why change more config files than necessary?

Apache 2 on Ubuntu There are two methods for setting up Drupal 5.x/6.x with Apache on Ubuntu. The first (preferred) method edits the 'Virtual Host configuration, which is the default setup on Ubuntu (even for a single-site webserver). The second edits the main apache2.conf, which is typical for an older setup.

Method 1: 'Virtual Host' setup First, from the linux command line, enable the rewrite module for apache with this command: sudo a2enmod rewrite

Next, use an editor (such as nano) to edit the appropriate Apache configuration file for your Drupal site in the /etc/apache2/sites-available/ directory. For a single site, the file is /etc/apache2/sites-available/default; if you have multiple sites, the file names should reflect the names of the sites to which they refer. Thus, to edit the default site configuration, use sudo nano /etc/apache2/sites-available/default

Look for the Directory section referring to the folder where your Drupal site lives (in /etc/apache2/sites-available/default, this is typically <Directory /var/www>), and change the line: AllowOverride None

to AllowOverride All

(See https://help.ubuntu.com/community/EnablingUseOfApacheHtaccessFiles for more information).


Save this file and then reload apache. sudo /etc/init.d/apache2 reload

Sub Domain Setup An alternative to multiple virtual hosts files is to use subdomains. This allows you to use a wildcard in the Server Alias. This allows for both a simple multi-site Drupal setup, as well as multi Drupal versions. Instead of multiple virtual hosts files, you define a subdomain for each drupal profile. Consider the following and modify your configuration file to fit your needs. 1. http://myproject.dr5.example/ 2. http://myproject.dr6.example/ 3. http://myproject2.dr6.example/ Here is an example of a partial listing of a virtual host configuration file that would support the last two lines in the above example. Note this is not intended to be a COMPLETE configuration file, but rather provide guidance for your development setup. <VirtualHost *> DocumentRoot "/www/Dr6" ServerName example ServerAlias *.dr6.example <Directory "/www/Dr6"> AllowOverride All </Directory>

Edit & save your config file to suit your development needs. Assuming the site is already enabled, then reload Apache.

Method 2: apache2.conf Then you have to enabled the rewrite module(mod_rewrite). You no longer have to do the: LoadModule rewrite_module modules/mod_rewrite.so AddModule mod_rewrite.c It's now as easy as: sudo a2enmod rewrite

To disable this module it's just: sudo a2dismod rewrite with Apache version 2, the httpd.conf has been deprecated and the new file is located at: /etc/apache2/apache2.conf

in this file you need to add your directory and the allow override to give access to your drupal site. so look for a section in your apache2.conf that has Directory tags and just add another section: <Directory /var/www/drupal_website_install> AllowOverride all </Directory>

*keep in mind that my website is in a subdirectory (drupal_website_install) and so you may need to edit the above to reflect this. By this I mean if i go into my webbrowser I need to go to http://localhost/drupal_website_install/ After you edit you apache2.conf as listed above you need to restart the server by: /etc/init.d/apache2 reload

Problems with Rewrite If you are having problems with getting your rewrite to work you can always use logging. To do that add this to the end of /etc/apache2/apache2.conf: RewriteLog "/var/log/apache2/rewrite.log" RewriteLogLevel 3

Level 0 is no logging Level 9 is log everything You can pick the level to determine the amount of output you need. ***Security Warning: Make sure to take the log code out, disable it, or put the log file in a directory that can't be read by normal users (as shown above) otherwise it can result in a security breach.*** hopefully this helps some people and saves them the time that I spent trying to get it working.

Finish - Enable Clean URLs Now browse to your site, login, click administer, find "Clean URLs" and browse to that page, run the test for "Clean URLs" (In Drupal 4.6 - 5.x this is buried in the paragraph explaining "clean Urls"). If your setup is like mine, you should now have Clean URLs in drupal.


Boost .htaccess for clean urls on specific shared hosts This code was taken from the Boost module and modified just a little bit. Please note that this implements a static cache of pages for anonymous users on your site. Read the Boost documentation to understand what this means. Various users have reported that this .htaccess file works on the following hosts: 1and1.com byethost.com dotster ehostpros.com ehosting.ca hostmonster.com IXWebhosting.com mediatemple.net siteground.com Site5.com godaddy.com There is no guarantee this will work for you though. Instructions: Create a text file, copy and paste the code below and save it as .htaccess Upload the file and put it in the public_html directory of your site. Modify "RewriteBase /" in line 68 to "RewriteBase /example" if your site is in subdirectory "example." # # Apache/PHP/Drupal settings: # # Protect files and directories from prying eyes. <FilesMatch "(\.(engine|inc|install|module|sh|.*sql|theme|tpl(\.php)? |xtmpl)|code-style\.pl|Entries.*|Repository|Root)$"> Order deny,allow Deny from all </FilesMatch> # Set some options. Options -Indexes Options +FollowSymLinks # Customized error messages. ErrorDocument 404 /index.php # Set the default handler. DirectoryIndex index.php # Override PHP settings. More in sites/default/settings.php # but the following cannot be changed at runtime. # PHP 4, Apache 1 <IfModule mod_php4.c> php_value magic_quotes_gpc php_value register_globals php_value session.auto_start </IfModule> # PHP 4, Apache 2 <IfModule sapi_apache2.c> php_value magic_quotes_gpc php_value register_globals php_value session.auto_start </IfModule> # PHP 5, Apache 1 and 2 <IfModule mod_php5.c> php_value magic_quotes_gpc php_value register_globals php_value session.auto_start </IfModule>

0 0 0

0 0 0

0 0 0

# Reduce the time dynamically generated pages are cache-able. <IfModule mod_expires.c> ExpiresByType text/html A1 </IfModule> # Various rewrite rules. <IfModule mod_rewrite.c> RewriteEngine on # If your site can be accessed both with and without the prefix www. # you can use one of the following settings to force user to use only one option: # # If you want the site to be accessed WITH the www. only, adapt and uncomment


the following: # RewriteCond %{HTTP_HOST} !^www\.example\.com$ [NC] # RewriteRule .* http://www.example.com/ [L,R=301] # # If you want the site to be accessed only WITHOUT the www. , adapt and uncomment the following: # RewriteCond %{HTTP_HOST} !^example\.com$ [NC] # RewriteRule .* http://example.com/ [L,R=301] # Modify the RewriteBase if you are using Drupal in a subdirectory and # the rewrite rules are not working properly. RewriteBase / # Rewrite old-style URLs of the form 'node.php?id=x'. #RewriteCond %{REQUEST_FILENAME} !-f #RewriteCond %{REQUEST_FILENAME} !-d #RewriteCond %{QUERY_STRING} ^id=([^&]+)$ #RewriteRule node.php index.php?q=node/view/%1 [L] # Rewrite old-style URLs of the form 'module.php?mod=x'. #RewriteCond %{REQUEST_FILENAME} !-f #RewriteCond %{REQUEST_FILENAME} !-d #RewriteCond %{QUERY_STRING} ^mod=([^&]+)$ #RewriteRule module.php index.php?q=%1 [L] # Rewrite rules for static page caching provided by the Boost module # BOOST START <IfModule mod_mime.c> AddCharset utf-8 .html </IfModule> RewriteCond %{REQUEST_URI} !^/cache RewriteCond %{REQUEST_URI} !^/user/login RewriteCond %{REQUEST_URI} !^/admin RewriteCond %{HTTP_COOKIE} !DRUPAL_UID RewriteCond %{REQUEST_METHOD} ^GET$ RewriteCond %{QUERY_STRING} ^$ RewriteCond %{DOCUMENT_ROOT}/cache/%{SERVER_NAME}/0/%{REQUEST_URI} -d RewriteCond %{DOCUMENT_ROOT}/cache/%{SERVER_NAME}/0/%{REQUEST_URI}/index.html -f RewriteRule RewriteCond RewriteCond RewriteCond RewriteCond RewriteCond RewriteCond RewriteCond RewriteRule # BOOST END

^(.*)$ cache/%{SERVER_NAME}/0/$1/index.html [L] %{REQUEST_URI} !^/cache %{REQUEST_URI} !^/user/login %{REQUEST_URI} !^/admin %{HTTP_COOKIE} !DRUPAL_UID %{REQUEST_METHOD} ^GET$ %{QUERY_STRING} ^$ %{DOCUMENT_ROOT}/cache/%{SERVER_NAME}/0/%{REQUEST_URI}.html -f ^(.*)$ cache/%{SERVER_NAME}/0/$1.html [L]

# Rewrite current-style URLs of the form 'index.php?q=x'. RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php?q=$1 [L,QSA] </IfModule> # $Id: boosted.txt,v 1.4 2006/12/05 10:39:19 arto Exp $

Drupal 6.x This .htaccess code works just fine for Drupal 6.4 BUT any internal links in the form 'directory/file.xxx' or just 'file.xxx' need to be changed to include a leading '/'.

Clean URL Support in Abyss This clean URL implementation has been tested on Abyss X1, one which has a properly set Custom Error Page 404 to any arbitrary file that, for this purpose, will be referenced as "/url_rewrite.php". Due to the generalized design of this solution, this method could theoretically work on virtually any webserver that can redirect missing URI location onto a php resource. The method described here works only on webservers with a singular host configuration. One esoteric requirement is for the webserver to pass the server variable REQUEST_URI containing the value of the original resource requested. For more information on configuring Abyss X1 for this purpose, please visit this Aprelium forum resource. This solution could be rendered academic if Aprelium finally decides to implement URL rewrite in Abyss internally. This method is useful for Abyss webservers version 2.3.2 and most other versions prior to this specific release. Another drawback is that the URL rewrite becomes only invisible to the machine but is always visible to the human. The idea is to pass (or redirect) the missing URI location on the HTTP-404 handler "/url_rewrite.php". For illustrative examples, let's look at the following scenarios: 1. http://www.example.com/node/add => "/node/add" not found, pass to 404 handler "/url_rewrite.php" => "/url_rewrite.php" determines "/index.php?q=node/add" exists and serves that instead. 2. http://www.example.com/admin/settings => "/admin/settings" not found, pass to 404 handler "/url_rewrite.php" => "/url_rewrite.php" determines "/index.php?q=admin/settings" exists and serves that instead. 3. http://www.example.com/no_exist/location => "/no_exist/location" not found, pass to 404


handler "/url_rewrite.php" => "/url_rewrite.php" determines "/index.php? q=no_exist/location" exists and serves that instead but lets Drupal display the proper "page not found" informational message. Here are the steps in letting this method apply to your setup. 1. Create the following file and save it as "/url_rewrite.php". <?php /* Add in this array the list of (old path => new path) pairs */ $redirection = array( '^(.*)$' => 'index.php?q=$1' ); if (!file_exists($_SERVER["REQUEST_URI"])) /* Get the URI and trim leading slashes */ $uri = ltrim($_SERVER["REQUEST_URI"], "/"); { foreach ($redirection as $key => $value) { if (eregi($key, $uri)) { /* Convert the replacement string syntax - $1 -> \1 */ /* and perform the substitution */ $uri = str_replace("index.php","",substr($uri,0)); $new_uri = str_replace("?","&",$uri); $new_uri = "/index.php?q=".$new_uri; $new_uri = str_replace("%26","&",$new_uri); break; } } } if (isset($new_uri)) { header("Status: 307"); header("Location: $new_uri"); exit; } ?> <!-- Your 404 error page --> <HTML> <HEAD> <TITLE>Not Found</TITLE> </HEAD> <BODY> The object <tt><?php echo $uri; ?></tt> is not available. </BODY> </HTML>

2. In the Abyss web console, enter the Custom Error Pages, add a 404 Status Code entry with the Associated URL value "/url_rewrite.php". Click OK and restart the Abyss webserver. 3. Test for functionality by querying your website directly with URI's such as: http://www.example.com/admin/settings http://www.example.com/node/add If the redirection works properly, proceed to the next steps. If the redirection would not work, check if the steps above have been strictly followed. Modify only those things that you have absolute knowledge of. 4. Log on to your website and log on to your "/admin/settings" page. Under General Settings section, enable Clean URLs. If the Clean URLs option is grayed out, add the line "$conf['clean_url'] = 1;" in your settings.php, then repeat this step. Don't forget to click the "Save configuration" button. 5. If things do not work out, completely remove the line "$conf['clean_url'] = 1;" from your settings.php. And browse to your http://www.example.com/index.php?q=admin/settings page to disable Clean URLs properly.

"No Tricks" Clean URL Support in Abyss 2.4 and later Version 2.4 of Abyss and later offers native URL rewriting support, so using tricks with 404 pages as outlined on this page is no longer necessary. Here's a better way to do it. 1. 2. 3. 4.

Log in to the Web Server Console at http://localhost:9999/ . Click the "Configure" button next to the relative host in the list. Click on the "URL Rewriting" icon. The "URL Rewriting Rules" list will probably be blank. Click the "Add" button at the bottom to add a new rule. 5. Our first rule is going to tell Abyss to not rewrite paths that are comprised of just a slash, or which contain a dot in them. (Or, more correctly, to rewrite them in exactly the way it found them.) The former are going to be requests for your home page, and the latter are most likely going to be requests for files which we don't want to pass through Drupal, like images, JavaScript files, etc. In the "Virtual Path Regular Expression" field, enter this: (^/$|(.*)\.(.*))

6. Make sure that the "If this rule matches" menu is set to "Perform an internal redirection," and enter simply this in the "Redirect to" field: $0

7. 8. 9. 10.

Check the "Append Query String" box. From the "Next Action" menu, select "Stop matching." All other check boxes should be left unchecked. Click OK. Abyss will take you back to the "URL Rewriting Rules" list, and probably give you a


message that it wants to restart. Ignore it for now until we add the second rule. Click the "Add" button again. 11. In the "Virtual Path Regular Expression" field, enter: ^([^\?]*)$

12. Again, we want to "Perform an internal redirection." Put this in the "Redirect to" field: /index.php?q=$1

13. Again, check the "Append Query String" checkbox, and set "Next Action" to "Stop matching." Click OK. 14. Now, if you see the two rules you created above in the URL Rewriting Rules list, take Abyss up on its offer to restart the server. 15. Visit your site in a web browser, log in as an administrator, open up the "Clean URLs" page in the "Site configuration" section, cross your fingers, and turn on clean URLs. Incidentally, we've found that Abyss works quite well with Drupal when configured this way; however, it was unable to cope with the volume of traffic our site was receiving, and often responded with page load times well into the double digits. I would not recommend using Abyss for anything but the most lightly-trafficked Drupal installations.

Caution There are some apparent weaknesses in the approach listed above as: ^/$|(.*)\.(.*). If we used Drupal's URL aliases feature, for example node 125 is aliased as "/mr.edmons_approach_to_the_tuna_recipe", the rewrite would fail as Abyss would be looking for a physical resource named "/mr.edmons_approach_to_the_tuna_recipe". A better approach has been described in this Abyss forum resource http://www.aprelium.com/forum/viewtopic.php? t=14948. In addition, the approach used in that solution transforms requests like ^(.*)\?(.*)=(.*)$ into /index.php?q=$1&$2=$3 to support multi-page indexes. And then, it uses REQUEST_FILENAME Is not a directory and REQUEST_FILENAME Is not a file as conditions, making it more accurate in leaving the file and directory requests intact. Via this approach, special cases can also be covered for Drupal installations located in subpaths.

Clean URL support in XAMPP Clean URLs do not work out of the box on XAMPP 1.5.x with PHP4 due to a problem in Apache's module load order; mod_rewrite will not work properly. To remedy this you will need to edit the file [path_to_xampp]/apache/conf/httpd.conf.

mod_rewrite location Remove the # at the beginning of this line: LoadModule rewrite_module modules/mod_rewrite.so

and move it to just above or below #LoadModule cache_module modules/mod_cache.so

AllowOverride If the mod_rewrite change does not work, you also need to check that AllowOverride All is set for the directory Drupal is in. Do this in httpd.conf or extra/httpd-xampp.conf Open up file \apache\conf\extra\httpd-xampp.conf Put this code in: Alias /drupal "C:/Program Files/xampp/htdocs/drupal/" <Directory "C:/Program Files/xampp/htdocs/drupal"> AllowOverride All Order allow,deny Allow from all </Directory>

Remember to change the path to match your installation location. If you're not sure where to add it, it might be worth putting it under the entry Alias /security "C:/Program Files/xampp/security/htdocs/" <Directory "C:/Program Files/xampp/security/htdocs"> ... ... </Directory>

which should already exist. Always restart Apache after you make changes or the changes to its configuration files won't have an effect.

Drupal Directory If Drupal is not installed in the document root, the next thing you'll have to do is modify the file .htaccess that comes with Drupal. Remove the commentsign (#) in front of RewriteBase and, if


necessary, modify the path: RewriteBase /drupal

Finally, in Drupal go to Administer Âť Site Configuration Âť Clean URLs (admin/settings/clean-urls) and run the clean URL test. Then click the "Enabled" option for Clean URLs and save the settings.

Clean URLs in Mac OS X Server For Mac OS X Server 10.4 (Tiger Server) and most likely previous versions as well, do not make changes to /etc/httpd/httpd.conf expecting the AllowOverride All directive to work. The correct file to add the AllowOverride All directive is in the directory /etc/httpd/sites/. In that directory are the virtual host configuration files. Each virtual server has a configuration file in that directory so it is in those files that you must enable AllowOverride All. If you only have one web server on your server configured, then the file you want to modify is /etc/httpd/sites/0000_any_80_.conf.

Clean URLs on IIS Drupal can display brief, "clean" URLs like those at drupal.org. For Apache sites, mod_rewrite powers this feature. On IIS you'll use either a third-party module or (on IIS7) Microsoft's URL Rewrite module to add this functionality. Refer to your specific IIS version below for details.

Some Third Party ISAPI Rewrite Modules ISAPI Rewrite by Helicon software. There is a free "lite" and a paid version. IIS Aid has an article on configuring the Helicon module. There is also an important note about .htaccess files with ISAPI_Rewrite 3 here. Check this thread for some approaches to setting up ISAPI_Rewrite. Ionic's ISAPI Rewrite Filter Micronovae's IIS Mod-Rewrite (the "standard" version works w/IIS5) (documentation).

IIS7 The best method is Microsoft's URL Rewrite Module for IIS7, available via the Web Platform Installer on Windows Server 2008 and Vista. Download and documentation are also available on Microsoft's IIS.Net site. You can also use third party rewrite modules (see list above). Note: Service Pack 2 for Windows Server 2008 & Vista contained important IIS7 bug fixes (KB954946) that affect how REQUEST_URI works. You can download the patch individually or SP2 from the MS website. IIS7 URL Rewrite Home contains useful explanations and links, including a walkthrough video. You will also need to enable (if you haven't already) FastCGI. After setting up the rewrite module and enabling FastCGI you will need to edit your site's web.config file. On IIS7, the web.config file replicates and replaces the functionality of .htaccess (which is included in your Drupal distribution). Here is the web.config file provided with the Acquia Drupal distribution for your reference (if you used the Acquia Drupal Web Platform Installer this is already included): <?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <!-- Don't show directory listings for URLs which map to a directory. --> <directoryBrowse enabled="false" /> <!-Caching configuration was not delegated by default. Some hosters may not delegate the caching configuration to site owners by default and that may cause errors when users install. Uncomment this if you want to and are allowed to enable caching --> <!-<caching> <profiles> <add extension=".php" policy="DisableCache" kernelCachePolicy="DisableCache" /> <add extension=".html" policy="CacheForTimePeriod" kernelCachePolicy="CacheForTimePeriod" duration="14:00:00" /> </profiles> </caching> --> <rewrite> <rules> <!-- rule name="postinst-redirect" stopProcessing="true"> <match url="." /> <action type="Rewrite" url="postinst.php"/> </rule --> <rule name="Protect files and directories from prying eyes"


stopProcessing="true"> <match url=".(engine|inc|info|install|module|profile|test|po|sh|.sql|postinst.1|theme|tpl(.php)? |xtmpl|svn-base)$|^(code-style.pl|Entries.|Repository|Root|Tag|Template|allwcprops|entries|format)$" /> <action type="CustomResponse" statusCode="403" subStatusCode="0" statusReason="Forbidden" statusDescription="Access is forbidden." /> </rule> <rule name="Force simple error message for requests for non-existent favicon.ico" stopProcessing="true"> <match url="favicon.ico" /> <action type="CustomResponse" statusCode="404" subStatusCode="1" statusReason="File Not Found" statusDescription="The requested file favicon.ico was not found" /> </rule> <!-- To redirect all users to access the site WITH the 'www.' prefix, http://example.com/... will be redirected to http://www.example.com/...) adapt and uncomment the following: --> <!-<rule name="Redirect to add www" stopProcessing="true"> <match url="^(.)$" ignoreCase="false" /> <conditions> <add input="{HTTP_HOST}" pattern="^example.com$" /> </conditions> <action type="Redirect" redirectType="Permanent" url="http://www.example.com/{R:1}" /> </rule> --> <!-- To redirect all users to access the site WITHOUT the 'www.' prefix, http://www.example.com/... will be redirected to http://example.com/...) adapt and uncomment the following: --> <!-<rule name="Redirect to remove www" stopProcessing="true"> <match url="^(.)$" ignoreCase="false" /> <conditions> <add input="{HTTP_HOST}" pattern="^www.example.com$" /> </conditions> <action type="Redirect" redirectType="Permanent" url="http://example.com/{R:1}" /> </rule> --> <!-- Rewrite URLs of the form 'x' to the form 'index.php?q=x'. --> <rule name="Short URLS" stopProcessing="true"> <match url="^(.*)$" ignoreCase="false" /> <conditions> <add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" /> <add input="{URL}" pattern="^/favicon.ico$" ignoreCase="false" negate="true" /> </conditions> <action type="Rewrite" url="index.php?q={R:1}" appendQueryString="true" /> </rule> </rules> </rewrite> <!-- httpErrors> <remove statusCode="404" subStatusCode="-1" /> <error statusCode="404" prefixLanguageFilePath="" path="/index.php" responseMode="ExecuteURL" /> </httpErrors --> <defaultDocument> <!-- Set the default document --> <files> <remove value="index.php" /> <add value="index.php" /> </files> </defaultDocument> </system.webServer> </configuration>

IIS6 On IIS6, you can use third party modules (see above) to add mod_rewrite-like functionality to IIS. You will also want to download and set up the FastCGI module.

IIS5 On IIS5, you can use third party modules (see above) to add mod_rewrite-like functionality to IIS. Due to improved application pool security implemented in IIS6+, deployment on IIS6+ is recommended. IIS5 Alternative: Creating a Custom Error Handler Note: This method seems to work for IIS5 but not IIS6+. You probably want to disable logging in IIS, since every page view is considered an error using this technique. make sure your Drupal is working well without clean urls enabled.


open your Internet Services Manager or MMC and browse to the root directory of the web site where you installed Drupal. You cannot just browse to a subdirectory if you happenned to install to a subdirectory. right click and select properties -> custom errors tab set the HTTP Error 404 and 405 lines to MessageType=URL, URL=/index.php. If you are using Drupal in a subdirectory, prepend your subdir before /index.php paste the following code into the bottom of settings.php file, which is usually located under sites/default/. the first two lines should be edited. If you aren't using a subdirectory, set $sub_directory to "". then set $active=1 and enjoy! <?php // CONFIGURATION $sub_dir = "/41/"; // enter a subdirectory, if any. otherwise, use "" $active = 0; // set to 1 if using clean URLS with IIS // CODE if ($active && strstr($_SERVER["QUERY_STRING"], ";")) { $qs = explode(";", $_SERVER["QUERY_STRING"]); $url = array_pop($qs); $parts = parse_url($url); unset($_GET, $_SERVER['QUERY_STRING']); // remove cruft added by IIS if ($sub_dir) { $parts["path"] = substr($parts["path"], strlen($sub_dir)); } $_GET["q"] = trim($parts["path"], "/"); $_SERVER["REQUEST_URI"] = $parts["path"]; if( array_key_exists( "query", $parts ) && $parts["query"] ) { $_SERVER["REQUEST_URI"] .= '?'. $parts["query"]; $_SERVER["QUERY_STRING"] = $parts["query"]; $_SERVER["ARGV"] = array($parts["query"]); parse_str($parts['query'], $arr); $_GET = array_merge($_GET, $arr); $_REQUEST = array_merge($_REQUEST, $arr); } } ?>

at this point, you should be able to request clean url pages and receive a proper page in response. for example, request the page /node/1 and hopefully you will see your first node shown. you should not use the q= syntax; use the clean url syntax. if you get an IIS error, you have a problem. please fix redo the above and then retest. browse to index.php?q=admin/system, enable clean URLS, and press Submit. you may get a php error if your php error reporting in your php.ini file is set to high. Try this setting in your php.ini file error_reporting = E_ALL & ~E_NOTICE

IIS CleanURLs using some of the available ISAPI filters. There is a free version called ISAPI_Rewrite Lite that should get clean URLs working for IIS.

General configuration: 1. Make sure IIS_WPG and NETWORK SERVICE have access to the .htaccess file - otherwise ISAPI_Rewrite cannot read the configuration httpd.ini (2.x) or .htaccess (3.x) files. 2. D5: Add $conf['clean_url'] = 1 to your settings.php - to manually enable clean urls. 3. D6: There are no changes required - it simply works.

ISAPI_Rewrite 3.x: You don't need to change the standard Drupal .htaccess file. Helicon ISAPI_Rewrite 3.x was designed to maintain maximum Apache mod_rewrite compatibility. Us the latest version of ISAPI_Rewrite 3.x (>=3.1.0.56 is required) with Drupal. If you cannot get it working out if the box - enable the LogLevel (http://www.helicontech.com/isapi_rewrite/doc/LogLevel.htm) setting and try to figure out what's broken in ISAPI_Rewrite.

ISAPI_Rewrite 2.x: Configuration example if Drupal is installed in webservers root [ISAPI_Rewrite] # Accept a url with the following directories and pass them through unchanged. RewriteRule /(?:misc|files|modules|themes|sites|uploads)/(.*) $0 [I,L] # Make URLs RewriteRule RewriteRule RewriteRule RewriteRule RewriteRule

sane /cron\.php $0 [I,L] /index\.php.* $0 [I,L] /install\.php.* $0 [I,L] /update\.php.* $0 [I,L] /xmlrpc\.php $0 [I,L]

# activate rewriting for custom modules (for e.g. banner.module) RewriteRule /banner_db\.php $0 [I,L] RewriteRule /banner_file\.php $0 [I,L]


# deactivate rewriting for custom modules (for e.g. robotstxt.module) RewriteRule /robots\.txt.* $0 [I,L] RewriteRule /(.*)\?(.*) /index.php\?q=$1&$2 [I,L] RewriteRule /(.*) /index.php\?q=$1 [I,L]

ISAPI_Rewrite 2.x: Configuration example if Drupal is installed in a subdirectory (Requires ISAPI_Rewrite >=2.9 Build 63) [ISAPI_Rewrite] # You must change/remove prefixes if Drupal is not installed in a subdirectory # Specify namespaces with UriMatchPrefix - DO NOT CHANGE ORDER UriMatchPrefix /drupal # Accept a url with the following directories and pass them through unchanged. RewriteRule /(?:misc|files|modules|themes|sites|uploads)/(.*) $0 [I,L] # Make URLs RewriteRule RewriteRule RewriteRule RewriteRule RewriteRule

sane /cron\.php $0 [I,L] /index\.php.* $0 [I,L] /install\.php.* $0 [I,L] /update\.php.* $0 [I,L] /xmlrpc\.php $0 [I,L]

# activate rewriting for custom modules (for e.g. banner.module) RewriteRule /banner_db\.php $0 [I,L] RewriteRule /banner_file\.php $0 [I,L] # deactivate rewriting for custom modules (for e.g. robotstxt.module) RewriteRule /robots\.txt.* $0 [I,L] # specify namespaces with UriFormatPrefix - DO NOT CHANGE ORDER UriFormatPrefix /drupal RewriteRule /(.*)\?(.*) /index.php\?q=$1&$2 [I,L] RewriteRule /(.*) /index.php\?q=$1 [I,L] # reset namespaces to default UriMatchPrefix UriFormatPrefix

Translating Apache's rewrite rules RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d In plain English: If the REQUEST_FILENAME variable does not exist (not an existing file and not an existing directory) then apply the rule: RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]" After much research this exact functionalty does not appear to exist in any ISAPI module for IIS. I think the following solution will help solve this issue. It probably works with most "ISAPI rewrite" modules (they just need the "stop the rewriting process" option). With this approach you basically revert the "Apache Rewrite" logic. First define rules for known files/folders (like /themes/ ..etc..). Have those "matching rules" exit, so rules processing stops before going to the next rule. With mod_rewrite.dll you can do that with the option [l]. Here is what my rules file looks (so far) like (using mod_rewrite.dll): RewriteRule ^/index.php\?q\=(.*)$ /index.php?q=$1 [l] RewriteRule ^/themes/(.*)$ /themes/$1 [l] RewriteRule ^/misc/(.*)$ /misc/$1 [l] RewriteRule ^/(.*)$ /index.php?q=$1 [l] Remember, you have to add an "exiting" rule for all known folders/files before hitting the final rule. The first rule is to avoid recursion and exit immediatly if the URL already has index.php?q=. To better understand the Apache rules here are the definitions of the main options: 'last|L' (last rule) Stop the rewriting process here and don't apply any more rewriting rules. Use this flag to prevent the currently rewritten URL from being rewritten further by following rules. 'qsappend|QSA' (query string append) This flag forces the rewriting engine to append a query string part in the substitution string to the existing one instead of replacing it. Use this when you want to add more data to the query string via a rewrite rule. REQUEST_FILENAME The full local filesystem path to the file or script matching the request. '-d' (is directory) Treats the TestString as a pathname and tests if it exists and is a directory. '-f' (is regular file) Treats the TestString as a pathname and tests if it exists and is a regular file. Note: Getting Apache running on MS Windows is not that bad (I had been delaying it for years). In reality it's a matter of a few hours to get going. http://www.sitebuddy.com aims to save you time in that endeavor.


Clean URLs with Easyphp. To get that the clear URLs works in Easyphp 1.8 you have to join the httpd.conf file and change this two lines: #LoadModule rewrite_module modules/mod_rewrite.so and #AddModule mod_rewrite.c and leave like this in the same line. LoadModule rewrite_module modules/mod_rewrite.so y AddModule mod_rewrite.c and Re-start the server. Easyphp have the mod_rewrite within. If there is any problem tell me. Oskar Calvo.

Clean URLs for EasyPHP 2 (with Apache 2) For EsayPHP 2 1. Uncomment this: #LoadModule rewrite_module modules/mod_rewrite.so

2. Under <Directory "${path}/www"> Change AllowOverride None

to AllowOverride All

Clean URLs with Lighttpd For those who have stepped up a notch in performance and moved from Apache to Lighttpd, you need to fix you clean URLs. There are two ways to do so:

Using url rewrites First you can use the following configuration for Lighty's mod_rewrite module: url.rewrite-final = ( "^/system/test/(.*)$" => "/index.php?q=system/test/$1", "^/([^.?]*)\?(.*)$" => "/index.php?q=$1&$2", "^/([^.?]*)$" => "/index.php?q=$1", "^/rss.xml" => "/index.php?q=rss.xml" )

The first line ensures that Drupal's clean URL check (when saving Settings) succeeds (Drupal makes an HTTP request for a path of the form /system/test/yLgnwqqUu5cWnvPi4Hrz.png). It's a special case that must be handled separately (read on for the reason). The two following lines let Drupal handle any URL that doesn't contain a dot. This is significant because we can assume, fairly confidently, that addresses like /node/add are Drupal URLs, but addresses such as /themes/bluemarine/style.css are physical files. So the above configuration will work for all cases where this assumption holds true; if there are exceptions to the rule, they can be manually added to the rewrite configuration. The last line handles the important exception of rss.xml, a Drupal URL that contains a dot. See also the related discussion at http://drupal.org/node/20766

Using mod magnet Second you can use lighty's mod magnet module together with a simple lua scripts. Have a look at the tutorials http://realize.be/drupal-lighttpd-clean-urls-made-easy or http://more.zites.net/lighttpd_and_drupal_clean_urls_flexible This way has the advantage that there are no problems with dots.

Clean URLs with Zeus


The Zeus server does not support all aspects of the Apache .htaccess file that ships with Drupal; in particular, the RewriteRules that allow clean URLs are not interpreted by Zeus. To set up clean URLs on a Zeus server, create a file called rewrite.script in the root of your Drupal installation, and put into it the following script: RULE_0_START: # get the document root map path into SCRATCH:DOCROOT from / # initialize our variables set SCRATCH:ORIG_URL = %{URL} set SCRATCH:REQUEST_URI = %{URL} # see if theres any queries in our URL match URL into $ with ^(.*)\?(.*)$ if matched then set SCRATCH:REQUEST_URI = $1 set SCRATCH:QUERY_STRING = $2 endif RULE_0_END: RULE_1_START: # prepare to search for file, rewrite if its not found set SCRATCH:REQUEST_FILENAME = %{SCRATCH:DOCROOT} set SCRATCH:REQUEST_FILENAME . %{SCRATCH:REQUEST_URI} # check to see if the file requested is an actual file or # a directory with possibly an index. don't rewrite if so look for file at %{SCRATCH:REQUEST_FILENAME} if not exists then look for dir at %{SCRATCH:REQUEST_FILENAME} if not exists then set URL = /index.php?q=%{SCRATCH:REQUEST_URI} goto QSA_RULE_START endif endif # if we made it here then its a file or dir and no rewrite goto END RULE_1_END: QSA_RULE_START: # append the query string if there was one originally # the same as [QSA,L] for apache match SCRATCH:ORIG_URL into % with \?(.*)$ if matched then set URL = %{URL}&%{SCRATCH:QUERY_STRING} endif goto END QSA_RULE_END:

For more details, tweaks, etc, see this forum post: http://drupal.org/node/46508

Enabling Clean URLs on a Netfirms Server I am setting up a site for a client on a Netfirms server. We need clean URLs, so I made the necessary changes to the .htaccess file (i.e., set the rewrite base to the proper location). I then went to the clean URLs page on the site, and ran the test. The URL display indicated that in fact clean URLs were working. Yet the radio button that would allow me to select clean URLs remained grey (that is, I could not select that as an option). Here is what I did to correct this and enable clean URLs: 1. Use phpMyAdmin to get into the database for the site. 2. Back up the database. 3. Click the SQL tab. 4. Enter the following into the query box: UPDATE `variable` SET `value` = 's:1:"1";' WHERE `name` = 'clean_url' LIMIT 1 ;

5. Click Go. 6. Return to the clean URLs page on your site and reload the page. 7. If the button is still greyed out, change a setting on your site. For example, go to site configuration, file system, and change public to private. and click Submit. 8. Return to the clean URL page on your site, reload the page, and it should now allow you to enable clean URLs. 9. Return to the file system config page and reset file download method to public. 10. Live happily ever after.

Example Clean URL configuration of


httpd.conf for performance Note the following directions do not adequately cover everything that needs to be included in a site's Apache configuration file to make sure the site is secure. The files directory needs a SetHandler directive per http://drupal.org/files/sa-2006-006/advisory.txt Enabling .htaccess in Apache requires more work on the part of the server. A full explanation can be found here: http://www.serverwatch.com/tutorials/article.php/3436911 Suffice it to say that it is in your interest to not use .htaccess files and even disable them if you are concerned about squeezing more performance out of apache. I moved the rewrite rules (all of the sample .htaccess file really) to /etc/httpd/conf/drupal.conf. Then for each virtual host, one can add the line: Include conf/drupal.conf

The rewrite rules change slightly: # Rewrite current-style URLs of the form 'index.php?q=x'. RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ /index.php?q=$1 [L,QSA]

Note that I added %{DOCUMENT_ROOT} and a leading slash in front of index.php -- because I have index.php installed in my document root. If you install it in another directory, you will want to give the relative path to it from document root with the leading slash. Important: If you forget the leading slash (more specifically, forget to give the relative path with leading slash), Apache will give you a "400 Bad Request" error. This is better. But we still have a problem where every request will check for the existence of a file and a directory before we apply the rewrite rule. The OS may be able to cache some of that information, but it would still be better to avoid the two file-system checks in the first place. There are some directories that we should not rewrite. And there are certain extensions that we should not rewrite. Using this information, we can update the rewrite rule to send everything to index.php that does not fall into this category. The first rule excludes the directories "files", "misc", and "uploads". The second rule excludes the extensions you see. Add more if you have other extensions in your directory that should not get passed to index.php. RewriteCond %{REQUEST_FILENAME} !^/$ RewriteCond %{REQUEST_FILENAME} !^/(files|misc|uploads)(/.*)? RewriteCond %{REQUEST_FILENAME} !\.(php|ico|png|jpg|gif|css|js|html?)(\W.*)? RewriteRule ^(.*)$ /index.php?q=$1 [L,QSA]

Note: This sort of directive will disable ImageCache and any other modules that depend on passing requests for files that are not found to index.php.

Compliance with SA-2006-006 Add this to your http.conf file; be sure to change the webroot path /var/www/html/ so it matches your webroot path. Also be aware that if your running drupal in multiple sub directories then you need to add this in for each one. (/var/www/html/sitea, /var/www/html/siteb, /var/www/html/sitec, ...etc). This is compatible with mulitsite due to the * in sites/*/files. <Directory /var/www/html/sites/*/files> SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006 Options None Options +FollowSymLinks </Directory>

See Directory Directive for the * syntax and other tips.

Getting Clean URLS Working on Mac OSX Tiger (Client not Server) The Apache htdocs directory in Mac OSX is by default located in /Library/Webserver/Documents. If you copied your Drupal files to this folder via the Finder, you will miss the .htaccess file with the mod_rewrite rules to allow clean urls because .htaccess is a hidden file that Finder does not see. To remedy this, open the Terminal in your /Applications/Utilities folder and type the following (this example assumes you have a copy of Drupal 5.2 on your Desktop): $ cd ~/Desktop/drupal-5.2/ $ cp .htaccess /Library/Webserver/Documents

Now go back and enable clean urls and you should be good to go.

Extra info Actually there's another issue caused by the default Apache configuration where the


AllowOverride value is set to None Open your httpd.conf file (I use the command pico /etc/httpd/httpd.conf) look for: # This controls which options the .htaccess files in directories can # override. Can also be "All", or any combination of "Options", "FileInfo", # "AuthConfig", and "Limit" AllowOverride None

and set allowoverride to All Save, restart apache and you're set!

Setting up clean URLs above web document root on virtual private servers To set up clean URLs above web document root on virtual private servers, you need to change a setting in httpd.conf. (default setting) Options FollowSymLinks AllowOverride None Change it to Options FollowSymLinks AllowOverride All None of the other changes discussed in this section of the handbook were necessary (and in fact had to be undone). The hosting provide said this does not provide any security issues (no guarantees).

reboot vps after making change This is excellent advice. I installed a test site at /var/www/test. My live site is at /var/www/html. The live site worked fine. In the test site i went to set clean url's and got an apache error. This fixed it. One minor note. I had to reset the vps before the changes took affect. With my hosting company this is simply a menu selection in their control panel, so if you have this setup and make the change and it does not work. Ask your provider to reset your Virtual Private Server. This is simular to stopping and starting Apache on a Windows install.

Burn a searchable site to CD using Server2Go Server2Go is a Webserver that runs out of the box without any installation and on write protected media. This means that you can use Server2Go to develop your Drupal site locally (or copy over a Drupal site you have already made) and then burn it to a CD. The CD version will be fully searchable and will run all Drupal's php magic! The clever part is that Server2Go copies all the databases to a temp folder once the CD is launched. This means your Drupal site will behave as if it is live so it can be searched etc. However, although you can also add content to the site when it is launched from the CD this will obviously not be recorded to the CD as the CD is read only! The next time you launch the CD you'll be back with your original burnt site. This tutorial is quite long, but the result is worth it in the end. 1. Download server2go mini package (NOTE: this tutorial has only been confirmed to work with the mini package [Apache 2.0.61], the package with the latest version of Apache did not work) 2. Unzip where you want it. The folder will be called 'server2go_a2_psm_mini'. In this tutorial we will rename the folder 'Drupal_S2G' to make file addresses easier to write out. Check Server2Go is working by double clicking the Server2Go.exe file. You should get the splash screen saying "congratulations...." opening up in Internet Explorer. 3. OPTIONAL STEP. You can skip this step if you can assume that anyone launching your site on CD will be happy using Internet Explorer. However, you can have Portable Firefox as the browser included on the CD. To do this: a. Download the latest version of Portable Firefox (tested working with Firefox 2 and 3). b. Install Portable Firefox in the 'Drupal_S2G' folder (click on 'Browse' to locate the folder on your computer). c. Launch Portable Firefox by clicking on FirefoxPortable.exe in (Drupal_S2G > FirefoxPortable). You can choose to disable session store. You can choose a blank page as the firefox home page in 'Options' and hide the Bookmarks Toolbar so you have a cleaner looking browser window when it launches. Close Portable Firefox. Opening and closing the application is important!


d. Go to (Drupal_S2G > FirefoxPortable > App > firefox) and rename firefox.exe to ffp.exe IMPORTANT: Leave the FirefoxPortable.exe in (Drupal_S2G > FirefoxPortable) unchanged! e. Create a file FirefoxPortable.ini in (Drupal_S2G > FirefoxPortable). You can use a program like Notepad++ to do this. f. Paste the following in the file: [FirefoxPortable] FirefoxDirectory=App\firefox ProfileDirectory=Data\profile SettingsDirectory=Data\settings PluginsDirectory=Data\plugins FirefoxExecutable=ffp.EXE AdditionalParameters= LocalHomepage= WaitForFirefox=true DisableSplashScreen=true AllowMultipleInstances=true DisableIntelligentStart=false SkipChromeFix=false SkipCompregFix=false RunLocally=false NOTE: THESE SETTINGS ARE FOR SITE DEVELOPMENT. THEY WILL NEED TO BE CHANGED WHEN BURNING THE SITE TO CD (see instructions below).

4.

5.

6. 7.

g. Open the pms_config.ini in (Drupal_S2G) and change the following: [line 52] BrowserType=PORTABLEFIREFOX [line 56] BrowserPath=FirefoxPortable/FirefoxPortable.EXE h. Check it works by launching the Server2Go.exe Change the following settings in the pms_config.ini file in (Drupal_S2G) the main folder (use a program like Notepad++ to change it): a. [line 21] StartLocal=1 (this is for using when you want to develop the website and have to write files. If you want to use server2go to run off a read-only CD at a later point, you will have to change it back to =0) b. [line 61] BrowserSize=MAXIMIZE NOTE: INTERNET EXPLORER ONLY. IGNORE IF USING PORTABLE FIREFOX c. [line 69] LocalMirror=0 this is so that the things you do to build your site are saved rather than carried out in a temporary folder d. Make a note of this value: 'HostName=127.0.0.1' [line 33] and this 'MySQLPort=7188' [line 80] as we need them later. Make the following changes in (Drupal_S2G > server > config_tpl > php.ini)to make sure Apache has the right settings to run Drupal, and can provide clean-urls: a. [line 201] max_execution_time = 60 (instead of 30) b. [line 202] memory_limit = 64M (instead of 8M) Change the following setting in (Drupal_S2G > server > config_tpl > httpd.conf) a. [line 164] Remove the '#' so it reads LoadModule rewrite_module modules/mod_rewrite.so Download Drupal 6.X (latest version) and unzip it into the htdocs folder. You can unzip it so that it sits in a folder inside the htdocs folder. This means you'll have a folder inside htdocs called 'drupal.6.4' or something similar. NOTE: YOU CAN IMPORT A SITE YOU HAVE ALREADY DEVELOPED. SEARCH THE DRUPAL HANDBOOK FOR ADVICE ON IMPORTING A DRUPAL SITE TO A LOCAL INSTALLATION. You can rename it to anything you want, but in this case we'll call it 'drupal' so all Drupal's files will sit in (Drupal_S2G > htdocs > drupal )

8. Do the things the Drupal set up will tell you to do which are: a. Create a folder called 'files' in the main drupal folder i.e.(Drupal_S2G > htdocs > drupal > files) b. Create a 'modules' folder and a 'themes' folder in the default folder, this is where you will place your add-on modules and themes i.e. (Drupal_S2G > htdocs > drupal > sites > all > modules) and (Drupal_S2G > htdocs > drupal > sites > all > themes) c. Copy the default settings file and rename it settings i.e. copy (Drupal_S2G > htdocs > drupal > sites > default > default.settings.php) and paste it in the same folder, re-naming it to settings.php 9. Now we're going to add the database that Drupal needs to run. a. Start server2go.exe in the 'Drupal_S2G' folder. It should open up the Server2Go startpage in a new browser (Internet Explorer if you didn't do the Firefox Portable Apps change) b. Go to the phpmyadmin link under 'Tools' on the right-hand side. c. Click on priveleges d. Click on 'Add user' e. Under username put 'admin' (or whatever you want to call your user); set 'host' as 'local' and set your own password (write it down!) f. Under database for user choose 'none' g. Under 'global priveleges' click on 'check all' h. Click on 'Go'. You should get a message saying 'You have added a new user' i. Click on the databases link and you'll see a box at the bottom of the page that says 'create database'. Put in the name of your database. Let's call it 'drupal' and press 'create'. You should get a message saying 'Database drupal has been created.'


j. Click on the picture of a house to go to the phpmyadmin home page. Then select 'databases' and click on 'drupal'. k. Click on the 'priveleges' tab at the top of the page and check that 'admin' is one of the users listed as having access to the database. All done! NOTE: READ OTHER DRUPAL HANDBOOK INFORMATION ABOUT SECURITY AND CREATING DATABASES. THIS IS A QUICK METHOD AND NOT NECESSARILY VERY SECURE... 10. Now we're going to set up Drupal. Go to http://127.0.0.1:4001/drupal/ (or http://127.0.0.1:4001/whatever-you-called-the-folder-you-unzipped-drupal...) and you should see the Drupal set-up page. a. After you've selected which language you are installing Drupal in, you will be asked to fill in the database details. In our case the database name is 'drupal'; the database username is 'admin'; and the password is whatever password you chose. b. Click on the 'Advanced options'. In 'Database host' put: 127.0.0.1 and in 'Database port' put 7188, the settings we noted earlier. c. Save and continue. d. Follow all the rest of the stuff as in the Drupal Handbook. e. As you're installing on a local site, you'll probably get this message: warning: mail() [function.mail]: Failed to connect to mailserver at "localhost" port 25, verify your "SMTP" and "smtp_port" setting in php.ini Try this: http://drupal.org/project/smtp 11. To check everything works, add some test content to your site and then close the browser. Restart Server2Go.exe once the server has shut down. If your content is there, bingo! It works! Settings for burning site to a CD If you want to run some tests without wasting loads of CDs you can create an ISO of your files (you should be burning all the files INSIDE 'Drupal_S2G' and not just copying the 'Drupal_S2G' folder itself onto CD, otherwise the autorun will not work) by using software such as PowerISO (the free version will create ISOs up to 300MB in size, which should be enough for testing). There are lots of nice settings to change, including the splash screen (Drupal_S2G > splash.png) which you could replace with any graphic you like and you can even change the name of the server i.e. replace 'Server2Go' with 'My Drupal Site on CD'. See the Server2Go website for more info. The following settings MUST be changed to make your site work on CD 1. In the pms_config.ini file in the (Drupal_S2G) folder: a. [line 21] StartLocal=0 b. [line 39] DefaultFile=drupal/ (or whatever the folder is called where you have your drupal files. Don't forget the trailing slash) c. [line 69] LocalMirror=1 If you are using Firefox portable, you will also have to change the following. Annoyingly, you can't be running firefox already when you start the CD and closing the browser will not shut down the server, this has to be done from the tray icon instead. 2. In the pms_config.ini file in the (Drupal_S2G) folder. a. [line 14] KeepRunningAfterBrowserClose=1 b. [line 17] ShowTrayIcon=1 (you need this to be able to shut down the server once you've finished with the CD). 3. In the FirefoxPortable.ini in (Drupal_S2G > FirefoxPortable ): a. AllowMultipleInstances=false b. RunLocally=true Comments welcome on this as the method has only been tested briefly.

Creating multiple sites on the same domain without using symlinks with Apache We were searching for a way to support multi-site installations (subsites) on the same domain, that is, to have: www.example.com/ www.example.com/sitea www.example.com/siteb www.example.com/sitec etc. with different content/themes/modules etc. but on the same Drupal 5.x installation. The normal way to do this is to create the appropriate /sites/www.example.com.sitea/settings.php etc... files and then to create recursive symlinks for each subsite, eg.


ln -s drupal/. drupal/sitea

etc.. The problem with this approach is that for multiple sites, it permits for recursive urls (http://www.example.com/sitea/siteb/sitea/siteb/content) which can play havoc with Google PageRank. For us a secondary problem was that is caused our IDE (eclipse) to loose it frequently, presumably since it wasn't able to distinguish the symlinks from real directories and so was recursively parsing the entire drupal directory until it eventually ran out of memory. And finally symlinks don't often seem to translate well in our versioning system, which means we needed to create a script to build the symlinks across different dev environments. What we wanted to do was achieve mutlisites on the same domain, but without using recursive symlinks. Instead our solution was to use an Apache RewriteRule for each site. eg. RewriteRule ^sitea(.*)$ $1 [PT]

This sits neatly in the .htaccess file (before the final 'RewriteRule ^(.*)$ index.php?q=$1 [L,QSA,PT]'), and can be versioned easily. The problem with this approach is that Drupal uses the SCRIPT_NAME environment variable (normally /index.php) from the server to figure out which site config file to use. With symlinks the server is 'tricked' into thinking it's actually accessing index.php at a different location (/sitea/index.php) than it actually is, so the SCRIPT_NAME is valid and the correct site file is found. RewriteRules don't affect SCRIPT_NAME though. In fact we weren't able to find any way to reliably modify SCRIPT_NAME just through Apache, so instead we patched bootstrap.inc to look at REQUEST_URI instead of SCRIPT_NAME (see attached patch file). This seems to work quite well, assuming you've set $base_url correctly for each site in it's settings.php file. (Editor's note: mod_rewrite has an 'env|E=VAR:VAL' (set environment variable) flag, add that to [L,QSA,PT] as appropriate) This solution is easilly versionable, requires no symlinks and doesn't lead to recursive URLS. Before you use this be aware of some known caveats of this approach: We've tried it this morning, it's hardly a tried and tested approach. There may well be issues we haven't uncovered. There are doubtless more elegant ways of doing this, and we'd love to hear from anyone who has done so. Solutions from the community seems rather sparse on this issue right now though, hence this post. This will only work on servers that set REQUEST_URI, as far as I know only Apache actually does this You'll need to make sure all links in your subsites add the appropriate $base_path to them. Attachment

Size

bootstrap.inc_.patch 1.3 KB

File and directory management These are general guidelines. 1. For Drupal 5.x and later: a new feature/best practice. For a normal (single site) installation, you should put all non-core modules or themes in the sites/all/modules or sites/all/themes folders. For a multi-site installation, put modules or themes in sites/all that you wish to have available for all sites. 2. Multisite setups allow you to have a modules directory specific to the site in the sites/www.example.com/modules directory. You can still put modules in directories under sites/all/modules if you want them available to all sites. The most specific module or version found is the one that gets used. If you have different versions of image.module stored in both sites/thatsite/modules/image and sites/all/modules/image, then 'thatsite' will use the version found in 'thatsite' directory, while the others will use the other one. 3. Leave the CHANGELOG.txt file in your root directory as it has the Drupal version information in it. If you manage more than one site, consider putting a version.txt file in the root of your drupal directory with the Drupal version, date and modules you are using. If you only manage a few sites you will probably remember them all, but if you set them up for other people, these reminders can help you if you are asked back to do additional work. Also, it can help the next site admin if you move on. 4. Rename update.php if you want. There are protections for it in the update script in that you have to be logged in with UID1. As of 4.7 update.php is now required for various module installations and other tasks so if you move/rename it for whatever reason do not forget to put it back to it's original state before module installs/updates. 5. Remove install.php if you want. This file is only needed during installation.

Other Tips Modules that are not part of core may or may not be supported by their contributor for a Drupal version upgrade. Avoid spaces in any directory name.

Installing Drupal 7 on an OLPC laptop


Installing Drupal 7 on an OLPC laptop This post has been moved to the Drupal for OLPC group on http://groups.drupal.org http://groups.drupal.org/node/24188

Run Drupal on multiple web servers behind a load balancer When running large Drupal installations, you may find yourself with a web server cluster that lives behind a load balancer. The pages here contain tips for configuring Drupal in this setup, as well as example configurations for various load balancers. In addition to a large selection of commercial options, various open source load balancers exist: Pound, Varnish, ffproxy, tinyproxy, etc. Apache can also be configured as a reverse proxy. The basic layout you can expect in most high-availability environments will look something like this: ┌─! Web server 1 Browser ──! HTTP Reverse Proxy ──┼─! Web server 2 Web server 3 └─! By way of explanation: Browsers will connect to a reverse proxy using HTTP or HTTPS. The proxy will in turn connect to web servers via HTTP. Web servers will likely be on private IP addresses. Use of a private network allows web servers to share a database and/or NFS server that need not be exposed to the Internet on a public IP address. If HTTPS is required, it is configured on the proxy, not the web server. Most HTTP reverse proxies will also "clean" requests in some way. For example, they'll require that a browser include a valid User-Agent string, or that the requested URL contain standard characters. In the case of Drupal, it is highly recommended that all web servers share identical copies of the Drupal DocumentRoot in use, to insure version consistency between themes and modules. The best way to achieve this is to use an NFS mount to hold your Drupal files. Note: If you plan to install Drupal on a web server that is accessible from the outside via HTTPS, there's an outstanding issue you'll want to check (#313145: Support X-Forwarded-Proto HTTP header). At this time, Drupal's AJAX callbacks use URLs based on the protocol used at the web server, regardless of the protocol used at the proxy. Your workaround is either this patch, or to avoid relying on AJAX on your site. Unfortunately, the Drupal installer relies on AJAX, so you'll either need to install the patch at the issue above, or install via HTTP instead of HTTPS.

Virtual sites: Create multiple sites based on one Drupal installation without using Drupal's multi-site feature Virtual sites offers almost the same (and more) functionality as the Drupal multi-site feature without the need for FTP or SSH. Depending on conditions (e.g. requested url or user role) handeld by the Condition(s) module, you can override theme, site information, menu's and more to virtually present the visitor with a different website.

What it can do Create virtual sites with different theme, site information, menu's etcetera. No need for FTP or SSH, like with Drupal's multi-site feature. Determine when to show what site based on (sub)domain, path, user role, taxonomy or any other condition. Add custom conditions by writing a plug-in for the condition module. Add custom virtual site features by writing a plug-in.

Features a virtual site can have Each virtual site can override (of your default Drupal site) the following settings: Set the source for the primary links Set the source for the secondary links Set the default site language Force a base URL (e.g. domain) Add HTML, JS and CSS to the page template


Set all site information settings (admin/settings/site-information) like name, slogan and front-page Set theme Set all theme-specific settings (admin/build/themes/settings/theme) like logo and even settings offered by the theme itself Any Drupal variable, using the easy INI syntax.

What it cannot do For the following settings you'd still need Drupal's multi-site feature: Database URL (DSN). Table prefix(es). PHP configuration and cookie_domain. String overrides.

Adding a virtual site feature It's really easy to add new settings (features) than can be applied to any virtual site you create. For this you can create a new module, just like the included virtual_site_common.module we will use here as an example. Like with any hook, replace virtual_site_common with the name of your module.

Hook in to the virtual sites module This can be achieved by writing an implementation of hook_feature_info(). <?php function virtual_site_common_feature_info() { return array( // The name of the method that will apply the settings. 'virtual_site_common_feature' => array( // The name for the settings as the title of the settings form. 'name' => t('Common'), // A description as it will be displayed on top of the settings form. 'description' => t('Set common settings for a virtual site.'), ), ); } ?>

As you might note, one module can offer multiples features. Just add another one to the returned array.

Return the form to set the feature settings Virtual sites will expect a method begins with the name of the specified 'apply method', but then postfixed with _form. This method receives an array just like the one it will return in the following paragraph and is expected to return a form array that will be displayed to set the feature settings. <?php function virtual_site_common_feature_form($context) { // ... (took out some code to make it more simple and clear) $menu_options = menu_get_menus(); $primary = isset($context['menu_primary_links_source']) ? $context['menu_primary_links_source'] : variable_get('menu_primary_links_source', 'primary-links'); $primary_options = array_merge($menu_options, array('' => t('No primary links'))); $form['menu_primary_links_source'] = array( '#type' => 'select', '#title' => t('Source for the primary links'), '#default_value' => $primary, '#options' => $primary_options, '#description' => t('Select what should be displayed as the primary links.'), ); // .. return $form; } ?>

Return the settings to be saved for the virtual site Virtual sites will expect a method begins with the name of the specified 'apply method', but then postfixed with _submit. This method receives the &$form en $form_state variables like any form and is expected return the variable (mostly an array) to be saved with the virtual site to be used in the following paragraph. <?php function virtual_site_common_feature_submit($form, &$form_state) { return $form_state['values'];


} ?>

Appliying the feature to the virtual site The 'apply method' specified in the hook_feature_info() method will be called if a virtual site is due. The method receives the same variable it returned one paragraph earlier and is expected to apply it's feature. Most of the time, this means overriding a setting in Drupal's $conf global variable. <?php function virtual_site_common_feature($context) { global $conf; if (is_array($context)) { $conf['menu_primary_links_source'] = $context['menu_primary_links_source']; } } ?>

Setting up a virtual site Understanding the Virtual Site module What the Virtual Site module does is basicly overriding Drupal settings. This is also what Drupal's multi-site feature does at core-level (bootstrap), but than at run-time level ( hook_init()). This difference is why Virtual Site cannot change database settings, because these are already initialized when Virtual Site comes in.

Understanding a single Virtual Site Each Virtual Site you create can be seen as a group of settings to override Drupal settings with. To determine when which Virtual Site settings are applied, the module depends on the Condition module. A condition can be seen as a group of requirements. When all requirements are met, the condition is TRUE.

A real-life example Let's say I want to have a different theme and site title for all pages that have a path alias starting with subsite/. Install both the Virtual Site and the Condition module. Go to admin/settings/condition/add and choose a name. Expand Requirement: Pages, set Validate to Only... and enter subsite/*. Press Save settings. Go to admin/build/sites/add and choose a name. Set Criteria to When ANY selected condition is met. Check the newly created condition and press Save settings. Find the newly created virtual site in the list and click the edit link. Go to the Information tab, change the default site name and press Save settings. Go to the Theme tab, select a theme and press Save settings. Optionally, change any theme settings and press Save settings. Create or go to a page having an alias starting with subsite/.

Handling multiple (sub)domains with Virtual Sites Just like with Drupal's multi-site feature, the key is to point all the (sub)domains to the main public HTML directory. You can ask your host to point all domains to the same public HTML directory or use symbolic links. Once set, you can create a new condition for every domain and use the hostname requirement to enter the domain you want the condition to be valid for.

Using CVS to maintain a Drupal website Why maintain a site from CVS? Maintaining a site from CVS helps in two main areas: 1. Upgrades: Normally, when it comes time to upgrade Drupal to a new version, there's a long list of upgrade steps that you need to follow. New tarballs need to be downloaded and extracted not only for Drupal core, but for all contributed modules as well. Repeat for each Drupal site you maintain. With CVS, this is just a single command. 2. Access to bug fixes: New releases of Drupal are only made after major bugs (generally security issues) are fixed. These bugs (along with dozens of smaller bugs) are always fixed in CVS first.

How to maintain a site using CVS Assuming you've already grabbed the core code and your favorite contrib modules using the


instructions on the checking out core from CVS and checking out contrib modules from CVS pages, you can update your files at any time using a couple of CVS commands.

Checking for updates to core and contribs cvs -nq update -dP

If updates are available, this will display output similar to: U modules/taxonomy/taxonomy.module U modules/upload/upload.module The 'U' means that the listed file is newer, or "updated" from the one you have in your local copy.

Updating the site To pull in the changes from CVS that will update your site to the latest Drupal "HEAD" version and dev versions for contributed modules: 1. Enter the command: cvs update -dP

This will update your site to the latest development versions of Drupal core and contributed modules. 2. While logged in as user/1, navigate to example.com/update.php to launch the database update utility to pick up any database changes.

Upgrading to a specific version of Drupal Your installation must have been installed via CVS in order to upgrade it via CVS. Let's say you'd like to upgrade from Drupal 6.6 to 6.8. (This will only update Drupal core, not contributed modules): 1. Enter the command: cvs update -r DRUPAL-6-8 -dP

2. While logged in as user/1, navigate to example.com/update.php to launch the database update utility to pick up any database changes. Note that if you are doing a major release upgrade (e.g. from Drupal 4.7 to 5.x or from Drupal 5.x to Drupal 6.x) you need to check on your contributed modules to make sure they have been ported for that major version of Drupal.

Upgrading to a specific version of a Drupal contributed module You can upgrade contributed modules to a specific version if those modules were installed via CVS: 1. Navigate to the directory at the root level of the contributed module. 2. enter the command: cvs update -r DRUPAL-5--1-4 -dP

where DRUPAL-5--1-4 is the "CVS Tag" as listed on the release notes for the module. 3. While logged in as user/1, navigate to example.com/update.php to launch the database update utility to pick up any database changes. Note: Core tags differ slightly from contribution tags. See Overview of core branches and tags and Overview of contribution branches and tags

Tips Having a test site is highly recommended; new changes from CVS could potentially bring in additional bugs or changes which break your site (particularly with contributed modules). Testing these updates on a copy of your live server before moving them over is a great idea. Using a version control system for your Drupal code such as CVS or Subversion is also a good idea; this way, you can keep track of what changes were made and when. The CVS deploy module is useful for printing more user-friendly version strings on the system modules page, and for interacting with the Update status module (included in core in 6.x and beyond, and via a contributed module for 5.x).

Apt-drup - Preserves original CVS keywords Preserving cvs keywords from Drupal (like $Id: drupal.js,v 1.29 2006/10/14 02:39:48 unconed Exp$) are essential to track down versions of files you originally used and to be able to patch your local copy of drupal in case you found a suitable patch on drupal.org. To be able to patch, you need to know which version of files we want to patch. These keywords might get lost when we import this to your own CVS and replaced with your keywords substitution: $Id: drupal.js, v 1.1 2007/01/15 11:30:00 owahab Exp$. This script does a "cvs export" from Drupal CVS for a version you can specify to a folder you can specify then it adds CVS keywords making the folder ready for a "cvs import" to your CVS, yet preserving Drupal's original CVS keywords.


The script also can be used to download Drupal modules the same way. 1. Download the attached file. 2. $ chmod +x apt-drup.sh 3. $ ./apt-drup.sh folder 5.1 You can substitute: folder with any folder name and 5.1 with any version of drupal. Note: you can directly request downloading a module a folder that doesn't exist and in this case drupal will be downloaded too. $ ./apt-drup.sh folder 5.1 event or $ ./apt-drup.sh folder 5.1 buddylist 1.0 http://cvs.drupal.org/viewcvs/drupal/contributions/sandbox/owahab/apt-dr...

cvsdrupal - checkout and maintain a local CVS repository This sophisticated script will check out Drupal and any number of contrib modules into a multisite (optionally) directory tree. It does check out the most recent files for tag selected in $REL, so if you do not want this, you'll have to cvs up to the tag you want. The file is attached for download - just change it from a .txt to a .sh file. #!/bin/sh # # cvsdrupal v0.0.3 20071101 cody@ray.name # # Multisite CVS Drupal Manager # # Initializes and maintains a multisite Drupal installation # from the CVS codebase. Additionally, this script manages # global and per-site modules and themes in the same manner. # # Run this daily or weekly with cron to keep your checkouts # up to date with all bug fixes and security patches # applied to your release in CVS. # # You define your site configurations in newline-delineated # string arrays. There are three classes of such configuration # arrays used in this script: SITES, _MODULES, _THEMES. # # For instance, you want to host two websites with shared # modules, example.net and www.example.com: # SITES=" # all # example.net # www.example.com # " # # The pseudo-site 'all' creates an 'all' subdirectory of 'sites' # in your Drupal installation. This is the parent of all shared # modules, themes, profiles, etc. # # Thus, you can create shared resources # all_MODULES=" # subversion # svn # " # all_THEMES=" # meta # " # # Each website you define (in SITES) is allowed its own modules # and themes independent of the other sites in your installation, # or it may use shared resources, or both. The decision is left # to you. # # Or, to continue our example, you can create per-site resources # example_net_MODULES=" # image # img_assist # " # example_net_THEMES=" # dichotomy # " # www_example_com_MODULES=" # diff # pathauto # " # www_example_com_THEMES=" # combustion # " # # Be sure to replace the dots with underscores in the names of the # per-site configurations. Also, note that any or all of these # configurations can be blank or left out altogether. # # The configuration above has been included as a skeleton for your # own website(s). See these included examples for further usage # details in context of the code.


# # License: http://www.gnu.org/licenses/gpl.txt # Change these settings to suit your environment: # # REL the CVS codebase version you want to checkout, # leave as is for a stable release or change to # HEAD for the very latest developments # # BASE root path in which the "drupal" folder resides # # UGID the local user:group file permissions, # set empty to ignore # # CHANGELOG: # 2007-11-01 Cody Ray <cody@ray.name> # * adapted for multisite usage # * with more sensible defaults # # 2006-06-28 Mark Constable <markc@renta.net> # * initial implementation # REL=DRUPAL-5 BASE=/var/www UGID=www-data:www-data # Add your sites SITES=" all example.net www.example.com " # Add shared modules all_MODULES=" subversion svn " # Add site-specific modules example_net_MODULES=" image img_assist " www_example_com_MODULES=" diff pathauto " # Add shared themes all_THEMES=" meta " # Add site-specific themes example_net_THEMES=" dichotomy " www_example_com_THEMES=" combustion " # Below here should remain untouched SRC=:pserver:anonymous:anonymous@cvs.drupal.org:/cvs/drupal export CVSROOT=$SRC [ -d $BASE/drupal/sites/all/modules ] $BASE/drupal/sites/all/modules [ -d $BASE/drupal/sites/all/themes/engines ] $BASE/drupal/sites/all/themes/engines [ -d $BASE/drupal/sites/all/themes/custom ] $BASE/drupal/sites/all/themes/custom [ -d $BASE/drupal/sites/all/themes/contrib ] $BASE/drupal/sites/all/themes/contrib [ -d $BASE/drupal/sites/default/modules ] $BASE/drupal/sites/default/modules [ -d $BASE/drupal/sites/default/themes/custom ] $BASE/drupal/sites/default/themes/custom [ -d $BASE/drupal/sites/default/themes/contrib ] $BASE/drupal/sites/default/themes/contrib

|| mkdir -p || mkdir -p || mkdir -p || mkdir -p || mkdir -p || mkdir -p || mkdir -p

if [ -d $BASE/drupal/CVS ]; then cd $BASE/drupal cvs -z6 up . -PAd -r $REL else cd $BASE cvs -z6 co -r $REL drupal fi for S in $SITES; do [ -d $BASE/drupal/sites/$S ] || cp -a $BASE/drupal/sites/default $BASE/drupal/sites/$S done export CVSROOT=${SRC}-contrib for S in $SITES; do N=`echo $S|sed -e 's/\./_/g'` for M in $(eval echo \$${N}_MODULES); do


if [ -d $BASE/drupal/sites/$S/modules/$M/CVS ]; then cd $BASE/drupal/sites/$S/modules/$M cvs -z6 up . -PAd else cd $BASE/drupal/sites/$S/modules cvs -z6 co -d $M contributions/modules/$M fi done done for S in $SITES; do N=`echo $S|sed -e 's/\./_/g'` for T in $(eval echo \$${N}_THEMES); do if [ -d $BASE/drupal/sites/$S/themes/$T/CVS ]; then cd $BASE/drupal/sites/$S/themes/$T cvs -z6 up . -PAd else cd $BASE/drupal/sites/$S/themes cvs -z6 co -d $T contributions/themes/$T fi done done [ ! -z $UGID ] && chown $UGID -R $BASE/drupal # vim: ts=4 sw=4 noet sts=4

Attachment

Size

cvsdrupal.sh_.txt 4.9 KB

Advanced: Using a repository to track local changes to Drupal code Note: many people use subversion (SVN) as their local repository even though drupal.org uses CVS. Hopefully someone will writeup the process here Note: The following assumes you have both basic knowledge of CVS and your own local repository set up and working. If you've been modifying the Drupal source code for your own purposes (or developing a module or theme) and manually applying your changes to the Drupal source every time it updates, you may be glad to learn that CVS can help make this easier. This is usually referred to as 'tracking third-party sources' and requires knowledge of the CVS concepts branching, release tags, and the vendor tag. We'll work through an example here and explain these concepts as we go.

Example Lets assume we'd like to track current Drupal CVS HEAD, and start by downloading the source. In this case we'll export using anonymous CVS (we could also just download a tarball). Begin by logging in to the anonymous CVS server, the required password is 'anonymous': cvs -d:pserver:anonymous@cvs.drupal.org:/cvs/drupal login

Then export the newest development version of drupal using the HEAD release tag: cvs -d:pserver:anonymous@cvs.drupal.org:/cvs/drupal export -r HEAD drupal

Now that we have a local copy of the drupal source we can import it into our own CVS repository. In this example we import with a log message including the date '-m "message text"', a module location/name of 'sites/drupal' (customize that to suit your own CVS repository), a vendor tag of 'drupal' and a release tag of 'HEAD20040110'. We also use the -ko option to prevent keyword expansion (this preserves the CVS $ Id $ tags used on drupal.org): cd drupal cvs import -ko -m "Import CVS HEAD on Jan 10th 2004" sites/drupal drupal HEAD20040110

Before we can customize we need to checkout into a working directory. Then we can modify a file or files and commit: cvs checkout drupal cd drupal ...modify a file or files... cvs commit

We now have a drupal module with a special 'vendor branch' (identified by the vendor tag), which contains the drupal source files we imported, and a main trunk with our modified files. Any files modified at this point are now HEAD on the main trunk of the module, whilst the unmodified files remain HEAD on the vendor branch (HEAD being what is produced by cvs update). For an individual file (fileone.php) the version history now looks like something like this: HEAD +-----+


[Main trunk]

fileone.php *------------+ 1.2 + \ +-----+ +---------+ [Vendor Branch] + 1.1.1.1 + +---------+ (tag:HEAD20040110)

Windows CVSNT On Windows using CVSNT, if you're having problems with binary files being imported as text (so that e.g. your *.png image files end up corrupted when you check them out), try cvs import -k+o ... instead of cvs import -ko .... Some experimentation (in CVSNT 2.5.03 on Vista) suggests that specifying "-ko" on the command line may override the default "-k 'b'" flag specified for *.png files in $CVSROOT/cvswrappers, while "-k+o" works OK. See also: http://www.cvsnt.org/manual/html/Wrappers.html. (Note that this will not change flags for files you have already imported into a repository, see http://www.cvsnt.org/manual/html/Binary-howto.html for information on how to fix these individually).

Additional resources Article by Nick Patavalis: The mechanics and a methodology for tracking 3rd party sources with CVS The section “Tracking third-party sources” in the CVS manual The section “Tracking third-party sources” in a book on CVS

Managing contributions Now I've been thinking about the best way to extend this method to incorporate contributions (eg, modules from the separate drupal.org contributions CVS repository or any other non-drupal-core code). I would welcome comments/suggestions on a good model. The model I'm currently using: 1. Maintain updated working copies of drupal HEAD and contributions HEAD from drupal.org locally, something like: [HOST:~/drupal]$ ls -1 contributions drupal imports

2. Import drupal into my local CVS repository with vendor/release tags as per method outlined above. 3. Maintain customized versions of drupal using CVS branches which can be diff'd/updated against the latest drupal I've imported into my local CVS repository as suggested at the end of the method above. 4. Make a temporary copy of the complete, updated directory of whatever contribution I want to add to drupal's core in a different and otherwise empty local directory so I can import it to my local CVS repository alone. For example, say I want to include the event module: [MP:~/drupal/contributions/modules]$ cp -r event ../../imports [MP:~/drupal/contributions/modules]$ cd ../../imports/ [MP:~/drupal/imports]$ ls -1a . .. event

5. Import the contribution with its own vendor/release tags into the proper place in the HEAD branch of drupal I already have in my local CVS repository. In this example, I import the entire event module from the temporary directory that contains the event module directory and only that directory. Note how the event module is imported into the proper directory (ie, "drupal/modules") of the drupal project in my local CVS repository: [MP:~/drupal/imports]$ cvs import -ko -m "Import module event HEAD on 04 OCT 2004" drupal/modules module_event MODULE_EVENT_20041004 cvs import: Importing /home/mediaped/repos/drupal/modules/event I drupal/modules/event/CVS N drupal/modules/event/CHANGELOG N drupal/modules/event/CREDITS N drupal/modules/event/INSTALL N drupal/modules/event/LICENSE N drupal/modules/event/README N drupal/modules/event/TODO N drupal/modules/event/event.css N drupal/modules/event/event.module N drupal/modules/event/event.mysql N drupal/modules/event/event.pgsql N drupal/modules/event/fields.inc cvs import: Importing /home/mediaped/repos/drupal/modules/event/po I drupal/modules/event/po/CVS N drupal/modules/event/po/event.pot N drupal/modules/event/po/de.po N drupal/modules/event/po/es.po N drupal/modules/event/po/he.po N drupal/modules/event/po/hu.po No conflicts created by this import

6. Now the contribution is included in the HEAD of the drupal project in my local CVS repository, so if I update a working copy of that local drupal project (using the update -d


option to ensure I get any new contribution directories) or checkout a new HEAD working copy, the newly added contribution will be in that working copy. 7. And, if I would like to add the contribution to one of the branches I have to maintain customized, separate versions of drupal, I just need to update a working copy of that branch with the release tag I established when importing the contribution into my local CVS repository. For example, to get the event module in a working copy of a branch tagged "drupalcustomA": [MP:~/projects/drupalcustomA]$ cvs -q up -d -j MODULE_EVENT_20041004 U modules/event/CHANGELOG U modules/event/CREDITS U modules/event/INSTALL U modules/event/LICENSE U modules/event/README U modules/event/TODO U modules/event/event.css U modules/event/event.module U modules/event/event.mysql U modules/event/event.pgsql U modules/event/fields.inc U modules/event/po/de.po U modules/event/po/es.po U modules/event/po/event.pot U modules/event/po/he.po U modules/event/po/hu.po

Note: if you try an update using your new tag and cvs complains that the tag doesn't exist, apparently you have to keep trying different cvs commands using that tag until cvs updates CVSROOT/val-tags. Once you get the tag to work once, it will work from then on. See the section here on error "cvs [checkout aborted]: no such tag". 8. This adds the event module files to that working copy, but I still have to commit them to that branch in my local CVS repository: [MP:~/projects/drupalcustomA]$ cvs -q ci -m "adding event module" Checking in modules/event/... ... done

9. Then I can see that the event module is now a part of that branch by checking the status -v of one of the event module files: [MP:~/projects/drupalcustomA]$ cvs st -v modules/event/INSTALL =================================================================== File: INSTALL Status: Up-to-date Working revision: Repository revision: Sticky Tag: Sticky Date: Sticky Options:

1.1.2.1 Tue Oct 5 06:46:30 2004 1.1.2.1 /repos/drupal/modules/event/INSTALL,v drupalcustomA (branch: 1.1.2) (none) -ko

Existing Tags: drupalcustomA MODULE_EVENT_20041004 module_event

(branch: 1.1.2) (revision: 1.1.1.1) (branch: 1.1.1)

10. Later, if there are significant changes to the event module, I'll update it using the same vendor tag, but a different release tag, just as it is demonstrated in the method above for drupal itself. My local drupal head will get the latest event module and I can get it in my customized branches by updating them with the new release tag I make when importing the new event module. 11. I will import every contribution I wish to include in my local CVS repository this way, giving unique vendor/release tags to each. That way I can pick and choose which contributions are included in any of my customized branches of drupal by updating each branch with the release tags of only the contributions I want in that branch.

Updating the vendor branch At some later point the drupal source code will have been updated and we'll want to add the updated version to our repository. We do this by repeating the process described above, we get a fresh copy of the source from drupal.org, and import using the same vendor tag but change the release tag from 'HEAD20040110' to reflect the newer version: cvs import -ko -m "Import CVS HEAD on Jan 11th 2004" sites/drupal drupal HEAD20040111

This updates the vendor branch, a single files revision history can now appear four different ways, depending on whether it has been modified by us, by the vendor (drupal.org), by both, or not at all. If the file was modified only by us, our modified version remains the head revision: HEAD +-----+ [Main trunk] fileone.php *------------+ 1.2 + \ +-----+ \ +---------+ [Vendor Branch] + 1.1.1.1 + +---------+ (tag:HEAD20040110)

If the file was modified only by the vendor, the new version becomes the HEAD revision: [Main trunk] filetwo.php *


\ [Vendor Branch]

\ HEAD +---------+ +---------+ + 1.1.1.1 +----------+ 1.1.1.2 + +---------+ +---------+ (tag:HEAD20040110) (tag:HEAD20040111)

And if the file was modified by both us and the vendor: HEAD +-----+ [Main trunk] filethree.php *------------+ 1.2 + \ +-----+ \ +---------+ +---------+ [Vendor Branch] + 1.1.1.1 +----------+ 1.1.1.2 + +---------+ +---------+ (tag:HEAD20040110) (tag:HEAD20040111)

Our version of filethree.php remains the HEAD revision, but this is clearly not desirable since it doesn't carry the latest changes. In fact, during our import of the latest source CVS would have warned us of conflicts between the two versions of filethree.php, we need to merge the changes to remove this conflict: cvs checkout -jHEAD20040110 -jHEAD20040111 drupal

Examine the merged file to ensure the changes CVS made were sane and then ‘ cvs commit’ the changes back to the main trunk. Leaving us with a new revision which becomes HEAD: HEAD +-----+ +-----+ [Main trunk] filethree.php *------------+ 1.2 +-------+ 1.3 + \ +-----+ +-----+ \ +---------+ +---------+ [Vendor Branch] + 1.1.1.1 +----------+ 1.1.1.2 + +---------+ +---------+ (tag:HEAD20040110) (tag:HEAD20040111)

Summary It should now be clear that using the CVS vendor tag to create a vendor branch in your own drupal module you can track changes to the drupal source code whilst also maintaining and developing your own customizations and new features for drupal. This example has been kept very simple for the purposes of explanation, but the basic process can be used to achieve many different things, some examples: Track a specific release of Drupal (e.g. 4.3, or 4.2), instead of the development (CVS HEAD) version. Maintain your customized sites with modules, themes, static pages, images etc all added to your CVS repository, whilst still tracking and importing updates to the drupal core. Branch your module to maintain several customized web sites off a single tracked branch of the drupal core. Reading the following additional resources is highly recommended.

Configuring Apache and PHP for Drupal in a Shared Environment These are some simple guidlines for setting up Drupal on a classic Linux-Apache-MySQL-PHP (LAMP) stack that provides a fair amount of security for the rest of your system.

PHP Configuration To start, some common PHP configuration options can be optimized. On some systems, these settings may already be set, but it never hurts to add them yourself just in case. PHP settings can be controlled in a number of ways. If you have access to the php.ini configuration file (usually found at /etc/php.ini), you can simply edit this file to cause systemwide changes. If you'd prefer to make these changes just for your Drupal installation, however, the best place to do so is in your Drupal installation's settings.php file. 1. First, increase PHP's memory limit to avoid getting out-of-memory errors when you begin to add a few modules. These errors may look something like this: Fatal error: Allowed memory size of 8388608 bytes exhausted (tried to allocate 30720 bytes) in /path/to/drupal/modules/system/system.module on line 1022 2. In your php.ini file, include a line that reads: memory_limit = 16M. Or… 3. …in your settings.php file, include a line that reads: <?php ini_set('memory_limit', '16M'); ?>


Apache Virtual Host Configuration If you have access to your system's Apache configuration files (typically /etc/httpd/httpd.conf or /etc/apache2/httpd.conf), you can also make some simple changes to simplify Drupal's multisite configuration for subdomains. ServerName example.com # Including a wildcard ServerAlias will direct all traffic to your end # users site and simplifies using Drupal's multisite configuration for # subdomains. ServerAlias *.example.com # This path will vary depending on your system. If you are using debian # make sure you drop the default images alias. Leaving it can be very # annoying to users on shared servers. DocumentRoot /var/www/example.com/public_html # # # # # # # # #

Place the log where your end users can see them, and make sure they are at the least readable to an end user. Performance option: You can move the Apache logs to another partition or drive to get some small increases in disk i/o on heavily loaded servers. You can symlink the log files to /var/www/example.com/logs/ to keep the persistent user interface, however it doesn't work in chroot environments.

ErrorLog /srv/www/example.com/logs/error.log CustomLog /srv/www/example.com/logs/access.log combined # Allow end users to manipulate their virtual hosts to their # hearts' content by enabling .htaccess files for them. AllowOverride All # Since mod_php doesn't like you to respect su_exec, and safe_mode is just # too paranoid to be usable, restrict php to the proper vhost but to a level # above the DocumentRoot so users have some private file space for # .htpasswd, Drupal files, backups, etc. php_admin_value open_basedir /var/www/example.com # Move PHP's temporary files into the open_basedir restricted space. php_admin_value upload_tmp_dir /var/www/example.com/tmp # session.save path isn't really necessary for drupal since it # stores sessions in the database, but for general php enabled #vhosts it keeps session data tied to each vhost. php_admin_value session.save_path /var/www/example.com/tmp

MySQL Performance Optimization Tweaking the configurations for your MySQL database server can give you a boost in performance. The MySQL configuration file is typically /etc/my.cnf. # big ol mysql.querycache(128M) (be aware of your systems memory limitations) query_cache_size = 134217728 # boost mysql max connections (min = (20*number of drupal sites)) set-variable=max_connections=512 eacclerator

ModSecurity Considerations ModSecurity is an Apache module that applies a set of rules to the activities of software run on Apache. It is used by some hosting environments to assure security, but some rules can interfere with the normal operation of Drupal. Because each ModSecurity administrator can write their own rules it is impossible to be certain that Drupal does not get caught up in these rules. It is possible for Drupal to get caught correctly. Before implementing the solutions below to turn of part or all of ModSecurity, be sure that Drupal is being caught improperly. Be certain that the activity being stopped is not due to some malicious or potentially dangerous activity and that the security of your site will not be compromised. It is possible, given the right permission or cooperation from your hosting company, to either turn off specific rules for your site or turn ModSecurity off entirely for your VirtualHost.

Turning off a Rule If you can find the errors put in the logs when your Drupal installation fails, you can identify the Rule ID and the location. Using this information you can disable a rule based on a path.


Here is an example based on a rule ID of 990011 that interferes with the Drupal update script at /update.php. In many environments, these log entries are not available to the hostee and must be examined by the hoster. Here is an example of a what will appear in the logs. You'll note the Rule ID and path are bold. [Wed Jan 20 11:43:01 2010] [error] [client 2.1.6.1] ModSecurity: Access denied with code 501 (phase 2). Match of "rx (?:^(?:application\\\\/x-www-formurlencoded(?:;(?:\\\\s?charset\\\\s?=\\\\s?[\\\\w\\\\d\\\\-]{1,18})?)?? $|multipart/form-data;)|text/xml)" against "REQUEST_HEADERS:ContentType" required. [file "/etc/apache2/modsecurity_crs/modsecurity_crs_30_http_policy.conf"] [line "69"] [id "960011"] [msg "Request content type is not allowed by policy"] [severity "WARNING"] [tag "POLICY/ENCODING_NOT_ALLOWED"] [hostname "d6test.domain.com"] [uri "/update.php"] [unique_id "R5esocASakAAHGeOqAAAAAI"] <LocationMatch "/update.php"> SecRuleRemoveById 990011 </LocationMatch>

Turning ModSecurity Off If you can change the VirtualHost record, this may be used to turn off ModSecurity for a specific VirtualHost. <ifModule mod_security2.c> SecRuleEngine off </ifModule>

Enable hosting of multiple sites from a single installation If you are running more than one Drupal site, you can simplify management and upgrading of your sites by using the multi-site feature. Multi-site allows you to share a single Drupal installation (including contributed modules and themes) among several sites. This is particularly useful for managing the code since each upgrade only needs to be done once. Each site would continue to have its own database and its own configuration settings so each site would have entirely different content and you can still specify the theme, modules and other settings for each site.

Configure a new site To create a new site using a shared code base you must complete the following steps: 1. Create a new database for the site. 2. Create a new subdirectory of the 'sites' directory with the name of your new site. 3. Copy the file sites/default/default.settings.php into the folder you created in the previous step. Rename the new file to settings.php. 4. Adjust the permissions of the new site directory, and grant write permissions on the configuration file 5. In a web browser, navigate to the URL of the new site and continue with the standard Drupal installation procedure. It may also be necessary to modify your Web server's configuration file (often named httpd.conf for Apache) to allow Drupal to override Apache's settings. This is true for all installations of Drupal and is not specific to the multi-site install. Additional information is available in the Best Practices: Configuring Apache and PHP for Drupal in a Shared Environment section of the Installation Guide.

Domains and URLs The new directory name is constructed from the site's URL. The configuration for www.example.com would be in sites/example.com/settings.php. You do not need to include 'www' as part of the directory name. Any sub-domain of example.com, including www, is served from that directory's settings unless there is an alternative, matching sub-domain sites directory. Installing a separate site in a subfolder is also possible. For example, example.com, sub.example.com, and sub.example.com/site3 can all be defined as independent Drupal sites using the following directory setup: sites/example.com/settings.php sites/sub.example.com/settings.php sites/sub.example.com/site3/settings.php Installing Drupal in a sub-folder is accomplished by adding a slash after the domain name and then the name of the sub-folder, as in the site3 example above. If you are installing on a non-standard port, the port number is treated as the deepest sub-domain. For example:


http://www.example.com:8080could

be loaded from sites/8080.example.com. The port number will be removed according to the pattern above if no port-specific configuration is found, just like a real sub-domain.

Domain-specific modules and themes Each site configuration can have its own site-specific modules and themes in addition to those installed in the standard 'modules' and 'themes' directories. To use site-specific modules or themes, simply create a 'modules' or 'themes' directory within the site configuration directory. For example, if sub.example.com has a custom theme and a custom module that should not be accessible to other sites, the setup would look like this: sites/sub.example.com/settings.php sites/sub.example.com/themes/custom_theme sites/sub.example.com/modules/custom_module

Document root One area of frequent confusion is that in a Drupal multisite installation the webserver document root is the same for all sites. For example with the following three sites: example.com, sub.example.com and example.com/site3 there will be a single Drupal folder and all sites will be calling the same index.php file. Some webhosts automatically create a folder (i.e. example.com) when creating a new domain. In this case it is necessary to create a symbolic link to the main Drupal folder.

Localhost On many systems it is possible to create entries in a "hosts" file to create aliases for the localhost name for a local workstation. By creating aliases for localhost it is possible to create names such as localdev1.example.com and localdev2.example.com, both for the local computer.

Domain name changes Once a site is in production under the folder sites, it should not be renamed, even if the Web site URL changes. This is because several database tables (for example: system, and files) include references to "sites/www.mydomain.com." Of course, instead of renaming the sites directory, you can create a symlink to the new URL from the old one. Navigate to the sites directory and then use the following command: $ ln -s /path/to/drupal/sites/old.domainname.com new.domainname.com

Comprehensive Multi-site Video Running a successful multi-site install requires knowledge of how Drupal handles multiple sites and how various features of Apache can be leveraged to make the multi-site much easier to manage. The video at this link provides a great deal of detail and comprehensive conf files for an Apache multi-site install of Drupal Video about Multisites vs. Multiple Sites at GotDrupal.com

Multi-site how-tos Do I need to copy each of these pages over to d7 or just reference them? Do we need to prune or rearrange them? Some of it seems redundant to other pages already created. http://drupal.org/node/43816

Multi-site on Linux Software installed: Drupal 7 Apache 1.3 Red Hat 6.1 (Although other distributions will work) PHP5 PostgreSQL 7.4

The process: 1. Install the Drupal core download from Drupal.org. 2. Install default Drupal site. I created mine with a "dummyUSER" user and "dummyDB" database with Drupal code in apache/htdocs/drupal 3. Once you have this working then create a new dir "example1.com" directly under "sites" dir.


mkdir sites/example1.com cp -R sites/default/* sites/example1.com/

4. Create a new database "example1DB" and new user "example1USER" in database. 5. Edit the sites/example1.com/settings.php and change the following: $db_url = 'pgsql://dummyUSER:dummyPSSWDl@localhost/dummyDB'; to $db_url = 'pgsql://example1USER:example1PSSWDl@localhost/example1DB';

(this step is optional, normally Drupal will recognise the base url automatically): 6.

# $base_url = 'http://www.example.com'; to $base_url = 'http://www.example1.com'; cp apache/htdocs/drupal/install.php to apache/htdocs/drupal/sites/example1.com/

(Remove the '#' [comment])

7. Point your browser to www.example1.com. Drupal will show you lot of errors. 8. Once it shows you errors you are all good. Just point your browser to www.example1.com/install.php and it will present you the installation screen. Install from there on. 9. Repeat the above process for www.example2.com Note If it says the site is offline then your database information in settings.php is not correct. If it says site not found then your base url in settings.php is not correct.

Multi-site on Mac To get multi-sites working on apache on a Mac, see "HowTo: Configure your local workstation to serve multiple sites using Drupal's multisite configurations and Apache's VirtualHost".

Multi-site on Windows-Apache Software This guide assumes you have installed WAMPServer, which you can download below: WampServer - includes Apache, PHP, and MySQL. Alternatively, you can install another WAMP package, but you will need to adjust the paths as necessary.

Process 1. Set up Drupal as usual 1. Install WampServer (or another 'WAMP' package) and make sure that Apache and MySQL services are running. 2. open phpMyAdmin in your browser localhost/phpmyadmin (or it can be launched via the WampServer icon in your system tray.) 3. Create a new database -- we'll call it drupal6 because this will be our base installation which we will leave untouched. While you're here create a second and third database, which we'll call site1 and site2. You can also change the database privileges if you want, but to keep things simple here, we'll stick with the default user root and no password. 4. Download Drupal 6 into WampServer's wamp/www folder (or the htdocs folder in Xampp and some other packages). (You'll need a tool like 7-zip to unpack the Drupal tar.gz file). 5. Do your first Drupal installation by pointing your browser to your Drupal folder, which should be something like http://localhost/drupal-6.x. Enter the drupal6 database and user settings (root and no password in this example). Complete the installation, but then leave it alone. 2. Prepare the files for your new multi-sites 1. Find Drupal's sites folder, make a copy of the default folder and rename it to the URL you want for your first test site. To keep it simple, we'll make it the same as the first test site database: site1. While you're here, create files, tmp and themes sub-folders within this new folder. The themes sub-folder will be used for any custom theme you create specifically for this site. Similarly, you can create a modules sub-folder for any module you use only with this site. However, generally, I find it easier to keep all contributed modules together in the sites/all/modules folder so I can use them with all my test sites. 2. Use a text editor to open the settings.php file in your new site1 folder and change $db_url line to reflect your first test site database, and the database user name and password, eg: mysql://username:password@localhost/site1

Also, change the $base_url to the above site URL, eg: $base_url = 'http://site1'; // NO trailing slash! 3. Repeat the above 2 steps for the other test site (site2) 3. Adjust windows so it can find the new site at a new made-up name Find the Windows hosts file, which should be in Windows' System32/drivers/etc folder, make a backup of that file and then open the hosts file in a text editor. Here, add new lines mapping your localhost IP address to match the URLs of your test sites and your original Drupal site. In our example, it would look like this: 127.0.0.1 localhost 127.0.0.1 site1


127.0.0.1 site2 127.0.0.1 drupal6

This is telling Windows Networking that http://site1/ is a name for this machine. 4. Adjust Apache so it responds to requests for the new names 1. Find the Apache httpd.conf file which, in WampServer, should be in the wamp/bin/ApacheX.X/conf folder (where X.X is the Apache version number). Sorry, I can't remember where Xampp installs this, but it will be in the Apache folder. Anyway, all you need to do with this file is make sure the "Virtual hosts" line is active by removing the # before Include conf/extra/httpd-vhosts.conf. 2. Make a backup of that httpd-vhosts.conf file (which is in Apache's conf/extra folder), then open httpd-vhosts.conf with a text editor and add these lines: <VirtualHost *:80> DocumentRoot C:/wamp/www/drupal-6.x ServerName site1 </VirtualHost>

where C:/wamp/www/drupal-6.x is the path to the Drupal installation folder and site1 is the URL for your first test site. Add another entry for your other test site, another entry for your original Drupal installation and another entry for WampServer's localhost URL (if you want to keep this active), eg: <VirtualHost *:80> DocumentRoot C:/wamp/www/drupal-6.x ServerName site2 </VirtualHost> <VirtualHost *:80> DocumentRoot C:/wamp/www/drupal-6.x ServerName drupal6 </VirtualHost> <VirtualHost *:80> DocumentRoot C:/wamp/www ServerName localhost </VirtualHost>

3. Restart Apache for the changes to take effect. With WampServer, this is done by selecting the WampServer icon in the system tray and then "Restart all services". 5. Now you're ready to install each Drupal test site using your browser, eg: http://site1/install.php http://site2/install.php

And that's it. You can access your two test sites anytime WampServer is running by using the http://site1 and http://site1 URLs. As I said, it's not exactly simple, but it is well worth the effort. Now you can do whatever you like with your test sites. If you mess up with, say, site1, just delete the site1 folder, the site1 database in phpMyAdmin and the site1 entries in the Windows hosts and Apache httpd-vhosts.conf files and start again. Alternatively, if a site works out and you want to go live with it, the multi-site approach makes this easier (in my opinion). One final caveat: I'm no expert. This technique works for me (apart from one corporate environment with a particularly annoying proxy server), but I'd welcome any feedback, especially if there are any accuracies.

Multi-site on Windows-IIS Prerequisites This guide assumes you have the following installed and running: Microsoft's Internet Information Services web server 6 or 7 PhP 5.2 or higher with PDO dlls enabled. The IIS FastCGI Module (IIS6 version here - in IIS7 it's an integrated module that needs to be enabled). MySQL If you want clean urls, you'll also need the URL Rewrite module (IIS7) or a third-party urlrewrite module set up at some point, though you can do this afterward.

Process 1. Install a site. This will be the parent site, providing the code-base for all the child sites. IIS7: be sure your parent site has a web.config file (see this for more info). Child sites will use the settings in the parent site's web.config file. To override these settings, put a web.config file with just the settings you want to override in the child site's folder (eg sites/site2.com/). 2. Create child site folders. Decide how you want people to navigate to your sub-site. In the parent site's /sites/ folder, create sub-folders for each new site you wish to install. Example 1: parentsite.com/subsite/ - in parent /sites/ folder, create /sites/parentsite.com.subsite Example 2: subsite.parentsite.com - in parent /sites/ folder, create /sites/subsite.parentsite.com Example 3: subsite.com - in parent /sites/ folder, create /sites/subsite.com


3. Copy the files & private subfolders. Go into the parent site's sites/default folder, and copy the /files and /private folders. Navigate to each new child site's folder (eg sites/parentsite.com/subsite/) and paste them. 4. Create child site settings.php file(s). Into each child site folder copy the parent's /sites/default/settings.php. Make each settings.php writable to IIS_WPG (IIS6) or IIS_IUSRS(IIS7). 5. Create IIS Site Mapping or Virtual Directory, depending on how you set up your site structure. IIS7 - Start the IIS Manager. Example 1: parentsite.com/subsite/ - Right-click on parentsite.com | Add virtual directory. Alias: [new site name, eg subsite]. Physical path: [parent site's physical path]. Click OK. Example 2: subsite.parentsite.com - Right-click on Sites | Add web site. Site name: [new site name, eg site2]. Click to select the application pool, and select the parent site. Physical path: enter the parent site's physical path. Host name: [new site url, eg site2.localhost]. Click OK. Example 3: subsite.com - add a web site as example 2, calling the host name subsite.com. 6. Set up the database(s). Multiple databases - If you want each site to have it's own database, create them now. For each child site, change settings.php to include each site's database name, username, and password: $databases['default']['default'] = array(<br /> 'driver' => 'mysql',<br /> 'database' => 'databasename',<br /> 'username' => 'username',<br /> 'password' => 'password',<br /> 'host' => 'localhost',<br /> );

Single database - See this tutorial. 7. Run the setup script for each site, eg http://site2.localhost/install.php.

Multi-site with single codebase, different content databases, shared user database, shared sign-on IMPORTANT NEW INFORMATION - In light of this comment regarding the ability of Drupal to handle different databases this HOWTO has been re-written. You do not need to use MySQL views at all to achieve this functionality. WARNING - Following this approach will make updates potentially trickier. It's not a major issue, but you must be mindful that you are sharing some tables amongst several Drupal instances, so you will need to be aware of install and update hooks which want to make changes to any shared tables. For example, it is possible for the update or install scripts of a module unique to one instance to break other instances if it makes changes to shared tables. There are other multi-site solutions, so plan carefully before selecting which one is right for you. This tutorial makes two assumptions: 1. You are _not_ a total newbie 2. You have downloaded MySQL GUI Tools (to make life easier) - clearly this is not a prerequisite - if you know and prefer command line MySQL then you can replicate the steps I provide easily enough: http://dev.mysql.com/downloads/gui-tools/5.0.html Make a copy of /sites/default to /sites/site1 and another to /sites/site2. You may add modules and themes directories in these new directories in the usual way for site-specific configurations. A files directory should be there already but create it if not. Rename /sites/site1/default.settings.php to settings.php and /sites/site2/default.settings.php to settings.php (and, for Linux, make sure both new files directories are writeable by the web user). Add these lines to your hosts files: 127.0.0.1 127.0.0.1

site1 site2

Create three empty MySQL databases: drupal_site1 drupal_site2 drupal_shared_tables

Go to http://site1 and run through the install process in the normal way, using the database name 'drupal_site1'. In MySQL Administrator create a new back-up project of the 'drupal_site1' database containing


In MySQL Administrator create a new back-up project of the 'drupal_site1' database containing _only_ the tables you want to share, save it and run it. The result should be a .sql file somewhere on your computer. For reference, the Drupal 6.x tables for sharing users are as follows: authmap profile_fields profile_values role sessions users

(This is not a definitive list. Nothing stops you from sharing taxonomy, etc. For example, with this configuration permissions are _not_ shared - each site has its own permissions matrix - but you might want to share one across all sites. Ditto with enabled modules.) Edit the .sql script you created and update the database name within to 'drupal_shared_tables'. Using the Restore interface, run the .sql script. You should now have a database called 'drupal_shared_tables' containing only the list of tables above. Go to http://site2 and run through the install process in the normal way, using the database name 'drupal_site2'. Go back to your original 'drupal_site1' database and drop the same list of tables from the schema. Ditto for the 'drupal_site2' database. Now our databases are ready. Browse to your Drupal application and edit /sites/site1/settings.php. Change these lines (usually starting at line 93 in Drupal 6.3): <?php $db_url = 'mysqli://user:password@localhost/drupal_site1'; $db_prefix = ''; ?>

To: <?php $db_url = 'mysqli://user:password@localhost/drupal_site1'; $db_prefix = array( 'default' => '', 'authmap' => 'drupal_shared_tables.', 'profile_fields' => 'drupal_shared_tables.', 'profile_values' => 'drupal_shared_tables.', 'role' => 'drupal_shared_tables.', 'sessions' => 'drupal_shared_tables.', 'users' => 'drupal_shared_tables.', ); ?>

Note the all important trailing dot on the end of drupal_shared_tables - this is vital! You're prepending table name info to Drupal's default table name, so you need the dot or Drupal will try to select: drupal_site_1.drupal_shared_tablesauthmap

Instead of the desired: drupal_shared_tables.authmap

Do the same for /sites/site2/settings.php, but with $db_url set to drupal_site2. You should now be able to login as the super user on both sites separately (http://site1/user or http://site2/user), using the credentials you entered when you installed site1, give them both different settings, modules, permissions and content but use the same user credentials. Moreover, if you create a user on site1, you will see them in users in site2, but with an entirely independent set of permissions you can decide on a site level. Now for the shared logins. Download and unzip the Shared Sign-On module in to /sites/all/modules. Enable it in both sites and go to the settings (admin/settings/singlesignon). Set the 'Master URL' to http://site1 for both sites. (Note, you could have two sets of sites with different 'Master URL' settings, effectively two shared sign-on groups - this is untested by me.) Save settings and logout. Clear your cookies to avoid any hang-overs from pre-shared-sign-on days. Go to http://site1/user and login. Now go to http://site2. You should stay logged in. Logout again and see you are logged out in both locations. Reverse it so you login on site2 and visit site1. Neat, huh? =) Need a new site? Take a copy of /sites/default to /sites/new-site, make the necessary vhosts (optional, see below) and DNS/hosts changes, create a blank database, run the Drupal installer, drop the tables you don't want from your new database (the shared ones), edit settings.php adding the $db_prefix array as above, configure Shared Sign-On and you're done! Optional Apache settings If you would like to set different Apache settings for your various sites (e.g. different access and error logs for each) add a vhost entry to Apache looking something like this: <VirtualHost *:80> DocumentRoot "c:/path/to/drupal" ServerName site1


ServerAlias site2 <IfModule log_config_module> CustomLog logs/d6_access_log.log common </IfModule> ErrorLog logs/d6_error_log.log <Directory "c:/path/to/drupal"> Options +Indexes AllowOverride All Allow from all </Directory> </VirtualHost>

This is not usually necessary. IMPORTANT - if you provide web services, the site hosting them _must_ have a line in the 'Target URL' box of Shared Sign-On settings (admin/settings/singlesignon) referring to each endpoint your site provides, for example: \/services\/xmlrpc$

This example is for the XML-RPC server provided with the Services module. Adjust to suit your endpoint(s). You do not need to do this if your site hosting the web services is also the 'Master URL'.

Notes on Multi-sites with Separate Code bases A simple Multi-Site install uses different databases for each site that the installation is hosting. In a nutshell, a simple Multi-Site extends a "Singular-Site" installation by the web developer (that's you) adding folders for each site that is served by the installation, in the "sites" directory.

The "default" Folder When you install a fresh copy of Drupal, a "default" folder is created inside "theRootOfYourInstallation/sites". Inside this folder resides a file called "settings.php" which contains this line of code:$db_url = 'mysql://username:password@pathTodataBase'; to securely access your database. During the initial database installation wizard, Drupal populates that particular line of code with values you entered into the fields. The reason this is called "default" is because, well, this is the default folder that Drupal looks into to find (in the "settings.php" file) they keys to access the databases. The "default" folder can also contain other items, such as a folder called "files" that contains the files for the default installation.

The Key to Understanding Multi-Sites For continuity, I will use "example.com" as the domain name for the "default" installation / folder and "doesthiswork.com" as the domain name for the site we are adding to this installation. Now that you know about the "default" folder, you can begin to understand how to add multiple sites to one installation: to add another site to this installation, 1. Create a sibling of the "default" folder with the domain name of "doesthiswork.com" . It is way important to use the extenstion (the .com or .edu, or .org). 2. Inside this folder, you will include a fresh version of "settings.php" (renamed from a "default.settings.php" taken from the zipped files of fresh Drupal core files) 3. Make sure that you have a fresh database created with no tables in it. Write down the path to the database, the username and password 4. Now you are ready to point your site to the root of the installation. The tricky part of this "simple" instruction is find where your DNS provider lets you point to another site. An example of a solution to this step is this: Say my "default" domain name (that goes to the default folder) is http://www.example.com and a domain /site that I want to add to this installation is http://www.doesthiswork.com. In the DNS panel for http://www.doesthiswork.com, I would point the CNAME to http://www.example.com. When Drupal gets a request for "doesthiswork.com", it will find the doesthiswork.com folder in sites. 5. The first time you go to "doesthiswork.com", you will be greeted with the welcoming Drupal Installation Wizard". Once you finish the installation wizard, the keys to your database kingdom will be saved an you will be free to enter your new Drupal site. So, what's the key to understanding how this works? It is: for every site that you add on this installation, you will need to create a separate domain name folder in sites with (at least) a "settings.php" file. Each folder/settings.php file points to a different database installation. Drupal uses the files stored on the multisite server, in addition to the data in each database to serve up the site. For each new site that you add, you will have to run through the installation wizard, setting up the site and adding content. (See installation profiles to make your life easier!) To reiterate: when you update a module, you will have to run "http://www.example.com/update.php" and "http://www.doesthiswork.com/update.php" (and other update.php's for each site on your multi-site install) to update EACH database. You might think that this is still alot of work... but it really is a time saver.


Remember to: put your sites in maintainence mode (admin/settings/site-maintenance)before run cron manually (admin/reports/status/run-cron) and clear the cache (admin/settings/performance) after you run update.php !

Additional Files to place inside your "default" sibling You can also have Files Theme Modules as folders in the "doesthiswork.com" folders. Those items will only be seen by the "doesthiswork.com" installation. As you might have guessed, items in the "theRootOfYourInstallation/sites/all" folder will be avaliable to ALL the site in your Multi-site install. If you place all your modules in the "theRootOfYourInstallation/sites/all" folder, when you upgrade to an newer version, you only have to replace the module there. However, you need to "update.php" on all sites in your multi-site... including your default site. One of the things that can trip you up is forgetting to create a "files" folder in your "doesthiswork.com" folder. Make sure you have done that to keep separate files from "different" sites.

Installation how-to articles Various special situations (e.g., installing Drupal behind a firewall, installing Drupal in a subdirectory of the webserver's document root) are discussed in this section. Information specific to particular operating systems or database platforms may be found elsewhere.

Beginner's guide for Cron on a shared hosting provider This guide is excerpted from the original article on ThemeBot.com: Time to talk about Cron - a beginner's guide If you have downloaded and installed Drupal 5, you will notice that there is an error in the administrative log when you first sign in with your administrative account. One or more problems were detected with your Drupal installation. Check the status report for more information.

When you look at the status report you can go ahead and click the link "run cron manually" and this will remove the error. However, you will eventually want to set up a Cron job to do this. Having the Cron maintenance run regularly on an automatic schedule is important for keeping your site indexed. If this is not done, new content that is added will not be included in search results. Cron can also perform other tasks in Drupal, such as cleaning up log files. Also, some of the contributed modules require that Cron maintenance is run regularly. With the Drupal 5 installation package, there is a script included for Cron and it is called cron.php. This file is located in the root directory of your Drupal installation. You can actually run Cron maintenance by entering, for example, the URL "http://www.yourwebsite.com/cron.php" into a web browser. We want to get this done automatically. You can achieve this one of two ways: using the cron service on your server or use the Poor man's Cron module. If you have the cron service available on your host, this is recommended since it will run more regularly. This example will use cron and the Lynx browser. Lynx is a text browser that is often installed on servers. You will need to contact your hosting provider to make sure that they have Lynx installed (most probably do). Once you know that Lynx is installed, you will need to find the configuration panel for setting up a Cron job in your hosting account. The first thing to do is enter the command. The following example is the command I use to make Lynx run cron.php: lynx -source http://www.example.com/cron.php

You may need to adjust this command depending on your hosting provider. The next step is to decide on a schedule to have this Cron job run. Running Cron does use system resources, so you don't want to have it running every minute if you don't need to. It is up to you to decide on an appropriate schedule for having each Cron job run. Understanding the format for entering a schedule can take a little getting used to. Here is a quick rundown on scheduling options that can be entered:


Minute — any integer from 00 to 59 (specifies the minute the job will be run on) Hour — any integer from 0 to 23 (specifies the hour the job will be run on) Day — any integer from 1 to 31 (must be a valid day if a month is specified) Month — any integer from 1 to 12 Weekday — any integer from 0 to 7, where 0 or 7 represents Sunday The asterik (*) specifies that the task should be run every time for any sub-constraint. This statement doesn't even make sense to me so let's just have a look at the examples to figure it out. You can also use the command */ to have jobs run every interval. Here the examples to help you get the hang of it. Run cron.php task every 30 minutes: */30 * * * * lynx -source http://www.example.com/cron.php Run cron.php task once a week at 1:00 am every sunday (you can see that sunday is represented by 0): 00 1 * * 0 lynx -source http://www.example.com/cron.php Run cron.php task on the first day of every month at 12:00 am (midnight) : 00 0 01 * * lynx -source http://www.example.com/cron.php I hope this is helpful for beginners who have installed Drupal and are wondering how to setup Cron. More information can be found under the Configuring cron jobs page in the handbooks.

Checklist for migrating to a new server Some things to look for when moving Drupal to a new host and taking your site live. This checklist is targeted at folk who have already successfully got a site running either locally or on a hosted server but need to move it to another host or architecture. There's (quite) a few things that can be different on different servers or versions, so there's a lot of extra testing that needs to be done, even if you think your site was solid to begin with. It looks like a big list, but most of these issues should be set correctly already. But they need to be confirmed before you can be confident your transfer will go without a hitch. Many you will have done before but you now need to remember to do again. If you've already uploaded without following this checklist and are now a bit stuck, you may find it neccessary to unset clean-urls manually before you can proceed with troubleshooting.

Server basics Verify Clean-Urls in Apache. Confirm AllowOverride All is in effect, and mod_rewrite is available. Ensure that when you uploaded, the Drupal .htaccess file went up as well. On some systems this file is hidden. Check DB support via phpinfo D7 requires not just MySQL but pdo_mysql as well.

Check GD image toolkit support in phpinfo, or in the Drupal admin status page. Decent php memory_limit (32 M) (phpinfo, php.ini) Decent php upload_max_filesize, post_max_size (phpinfo, php.ini) For Drupal to behave as expected, post_max_size must be 2x upload_max_filesize Check allow_url_fopen for updates and aggregation if required. Check that the include_path contains '.' Check that safe_mode is off Check the error_reporting level. You may want it on when installing, but remember to turn it off on production. This and more is covered in the Drupal 'requirements' doc and of course INSTALL.txt is the first reference to look at. Set up cron! Remember! If your host is Debian/Ubuntu less than php5/5.2.6-2 PHP sessions may not get cleared correctly, and you may need a workaround for Debian session garbage collection

Drupal UI TEST an upload. TEST an image derivative rebuild. Review access control settings for admin/editor/authenticated/anonymous users. Log in as each role and check a few things like the ability to upload big files, use input filters. I've been stung a few times by setting up as user #1 and handing over to an 'editor' role client who then found they still had incorrect upload size limits etc.

Permissions checks Adjust write-access to sites/${SITENAME} (if installing) and sites/${SITENAME}/files Check at admin/settings/file-system, it'll warn you if there's a problem. What you need to do and what you CAN do to adjust permissions on the server will vary between systems. check write-access to /tmp . Change it to sites/${SITENAME}/files/tmp (no leading slash) if needed.


Security tweaks Once you are set up, consider locking all the code to read-only for security. See more at File Permissions and Ownership For Security. Check the owner and group of any newly created files and folders ls -la files/ If the owner of web-process created files is the same as your login username (not www-data), that means it's theoretically possible for web scripts to modify themselves. This is a hack vector, and could be a concern.

Check access to your settings.php. It contains your database password in plaintext, and it would be good to NOT have that readable by anyone other than your web process. Unfortunately that means it's readable by anyone running the web process on that machine. For this reason, it would be good to have your : - Admin-control panel-ssh login password different from your - database password different from your - Drupal user #1 password. ...even though that's a pain. If you have enough rights, consider shifting htaccess directives into apache configuration files for performance. Remove phpinfo.php if you had one.

Testing TEST directory browsing is disabled for files/ (by attempting to access it in a browser) TEST that www.example.com and example.com perform the same, preferably by redirecting one to the other. See .htaccess [handbook doc needed?] TEST email sending. Email setup is different for different hosts, but should be configured already by the sysadmin. Check the email:from headers and see what you can do to avoid system emails going into a spam bucket More testing ideas.

Best Practice Extras Run a links checker. Xenu is simple for windows. linklint is adequate for commandline. Visit your admin/reports after running a link checker and see what shows up. Other errors, not just 404s may appear there and need attention. Consider benchmarking Drupal Performance. apachebench (ab) is OK, but needs root and just whacks one page. siege is better (can script a session), but needs installing. If you have a high level of control on the server, Consider other tuning issues like PHP accelerators. Consider your backup plan. TEST your backup recovery. Remember to configure and test any non-Drupal logging tool you may want to use. eg, Webalizer, Google Analytics or something provided by your host. Now is a good time to locate your server logs, and make sure you have access to them from your host. When you need them to troubleshoot problems it's too late to find that they don't exist.

Document every password, path and IP you can find. Domain registration, Control Panel login, User account info, FTP server, MySQL connection string, location of the config files for your backups, contact details for key parties etc. Put it all in a document with a DATE in it. Print that off and save it in three SAFE places. Review your hosts policies on overages. It's much better if your host provides a warning & throttle service when/if you overrun your bandwidth or storage allocation than if they let it run then charge and penalize you (or just shut you down). Some hosts also punish 'excessive' use of the database or processor - which Drupal can be guilty of. Read the fine print. References: Server and database configuration settings

Complete Drupal install shown by screencast The screencast at http://mrdychko.info/node/38 gives a complete walkthrough for installing Drupal on shared hosting with Hostmonster.com. It was recorded live during a professional development session for high school teachers. It includes: sign-up process, uploading and extracting files, creating a MySQL database, running the Drupal install script, installing modules, configuring php.ini to allow larger file uploads, creating a new content type which includes a file upload field, creating a view (with the Views module) to display uploaded files, and setting up a cron job. All in 42 min and 24 seconds!

Configure .htaccess to allow awstats to work with clean URL's When I installed Drupal with clean url's it made my Awstats (within the cgi-bin) not work and just return a Drupal page not found error. Drupal is installed in the root directory (public_html) of my web space so is the cgi-bin folder that Awstats is in.


I fixed the problem by changing the protect files and adding 1 rewriteCond line to the .htaccess file as follows: In the FilesMatch section, find " |code-style\.pl " without the quotes and remove it from the following code: # Protect files and directories from prying eyes. <FilesMatch "(\.(engine|inc|install|module|sh|.*sql|theme|tpl(\.php)? |xtmpl)|Entries.*|Repository|Root)$"> Order deny,allow Deny from all </FilesMatch>

Now add this line (RewriteCond %{REQUEST_URI} "!cgi-bin/") to the following: # Rewrite current-style URLs of the form 'index.php?q=x'. RewriteCond %{REQUEST_URI} "!cgi-bin/" RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]

This was all done on the default install of Drupal w/.htaccess.

Configuring .htaccess to ignore specific subfolders Ignoring Subfolders that exist in the DocumentRoot With clean URL's enabled, when running other software applications in subfolders (subdirectories) of a Drupal root installation. your .htaccess file may rewrite those URL's to Drupal. This may be a particular problem for those with a Drupal installation in the root of their domain using Cpanel and Fantastico where Fantastico installs other software into subfolders. For example, phpSurveyor's admin interface as installed by Fantastico will not work with Drupal's default .htaccess settings. The URL for the admin interface is inaccessible and will return a "page not found" page in your Drupal site. The trick is to modify .htaccess to ignore specific files/folders. So for example, if you have two folders, <folder1> and <folder2> in the root of your Drupal installation, modify your .htaccess file by inserting the following code directly after the "RewriteEngine on" directive, before the Drupal rewrites: =========[ start of .htaccess snippet]========== <IfModule mod_rewrite.c> RewriteEngine on # # stuff to let through (ignore) RewriteCond %{REQUEST_URI} "/folder1/" [OR] RewriteCond %{REQUEST_URI} "/folder2/" RewriteRule (.*) $1 [L] # ====================[ end ]=====================

For each folder you want to bypass, add a RewriteCond line, and end all but the final RewriteCond with [OR]. Note that the [L] in the rewrite rule tells it to stop there and bypass the rest of the rewrite rules.

Ignoring subfolders that are included via Apache Alias directives As of 4.7, files and directories should be automatically allowed through in drupal's .htaccess setup. Thats what the !-f and !-d lines do. However if you are working with Apache Alias or similar directives the file doesn't actually exist so drupal will take over like it should. The best way around it is to just add one more conditional that matches your location and make it skip it too. Thats what the ! means. Please see below: RewriteCond RewriteCond RewriteCond RewriteRule

%{REQUEST_URI} !^/yourDirectoryName %{REQUEST_FILENAME} !-f %{REQUEST_FILENAME} !-d ^(.*)$ index.php?q=$1 [L,QSA]

It essentially means Apply this rule if the REQUEST_URI doesn't start with /yourDirectoryName and the REQUEST_FILENAME isn't a real file or a real folder. Which is exactly what you want. There is an implied "AND" between the lines of that rule. The ! says "not like this".

Restore support for other Directory Index Files (index.htm) Drupals .htaccess file also includes a line (DirectoryIndex) that gives instructions "If asked for a directory path, return the index.php file found in that directory". This replaces the normal webserver behaviour that usually says "return the index.htm file found there". The good news, it that it's easy to say both. And that's what we need to do to allow other folders of


HTML content to co-exist with Drupal and server them as expected.

Method 1 - Allow index.html anywhere Edit Drupals .htaccess file and change the DirectoryIndex line to read: DirectoryIndex index.php index.html index.htm

Now index.htm and .html files may be served if no index.php is found. You can add other filenames as needed.

Method 2 - Allow html (and folder listings) in the subdirectory You may also avoid changing Drupals .htaccess, and just set the preferences for the relevant subfolder. This is a little more specific, accurate, and maybe secure. Create a new .htaccess file in the target folder, eg folder1/.htaccess Add the lines DirectoryIndex index.php index.html index.htm Options +Indexes

Now index.htm and .html files may be served from there. Additionally, the default Apache 'raw folder listing' behaviour has been restored, so you can see where you are even if there is no index file in the folder. This is optional, but may be useful to you. It is advised you do not allow folder listings at the Drupal level. There's a heap more possible, but it's all in the Apache Docs so go there for more info and examples. You may also want to adjust the 404 behaviour for that subfolder too.

Create Drupal database using Plesk Create the database Login and select Services Âť Databases This loads a page with an overview of databases. Depending on your host a database may already have been created. This is often the case with low-budget hosts, where you are limited to one database. In that case, use table prefixing. If no limits exists it is best to create a new database for Drupal via: Add New Database. Click on the name of the database you wish to use. In the overview page you'll see all users for this database. Again, depending on your host, you need to create a new user via: Add New Database User. If you do not have the option to create new database users, use a pre-existing one. Load the Drupal database scheme From the database users overview page click DB WebAdmin The icon is a blue stack with a monkey wrench. Note that this opens phpmyadmin in a pop-up that may be blocked by your browser. First make sure the relevant database is selected. If necessary, click Databases in the phpmyadmin start screen and then the database name. Go to the SQL screen via a small button labelled SQL in the left pane of the phpmyadmin screen. If you've created a new user for the Drupal database, you need to grant priviliges to the user: execute the SQL query: GRANT ALL PRIVILEGES ON databasename.* TO username@localhost IDENTIFIED BY 'password';

where 'databasename' is the name of your database 'username@localhost' is the username of your MySQL account 'password' is the password required for that username This may not complete successfully; in that case you'll have to assume / hope the user was created with the necessary privileges. To create the database layout, select the tab import files. Upload the file database/database.mysql with the database scheme. Having had the virtual server I use upgraded to Plesk 7.5.4, I attempted to install drupal. I am having no joy, which is a pretty big disclaimer to my comment here - see http://drupal.org/node/20397 - but I think it is unrelated to this. I can't grant all privs to the mysql user as per this page. I think this article is out of date as a) the db user plesk creates doesn't have the grant privs so there's a difference in Plesk versions going on here and b) there is no way for the end-user to set this so it follows that Plesk does this for you, which ties up with the fact I can upload the schema and the users are being added.


I checked the mysql documentation and couldn't find a command to report the priviledges a user has, so I couldn't verify this directly.

Create a custom php.ini This article explains how to set up a custom php.ini that overides your servers default php.ini, providing an easier alternative (in the long run) than using .htaccess to overide php.ini settings. Itassumes that you have a ssh account set-up and are using a ssh client (e.g. putty http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html). You are also expected to logically replace directory structures given here, with your own, if they are different. Procedure for allowing php.ini configuration for a website (shows changing file upload max) 1/ Create a cgi-bin Directory First you’ll need a cgi-bin directory: mkdir ~/[your website directory]/cgi-bin/

This directory will be hosting your copy of php and your php.ini file. 2/ Create a script to retrieve the latest copy of php.cgi and php.ini Make a file in ~/ called php-copy.sh containing the following, where 100M contains whatever file size limit you like, and [your website directory] is appropriately substituted. For PHP4: #!/bin/sh CGIFILE="$HOME/[your website directory]/cgi-bin/php.cgi" INIFILE="$HOME/[your website directory]/cgi-bin/php.ini" rsync -a /dh/cgi-system/php.cgi "$CGIFILE" # REMOVE THE FOLLOWING LINE TO CREATE THE UPDATE-ONLY SCRIPT: cp /etc/php/cgi/php.ini "$INIFILE" perl -p -i -e ' s/.*post_max_size.*/post_max_size = 100M/; s/.*upload_max_filesize.*/upload_max_filesize = 100M/; ' "$INIFILE"

For PHP5: #!/bin/sh CGIFILE="$HOME/[your website directory]/cgi-bin/php.cgi" INIFILE="$HOME/[your website directory]/cgi-bin/php.ini" rsync -a /dh/cgi-system/php5.cgi "$CGIFILE" # REMOVE THE FOLLOWING LINE TO CREATE THE UPDATE-ONLY SCRIPT: cp /etc/php5/cgi/php.ini "$INIFILE" perl -p -i -e ' s/.*post_max_size.*/post_max_size = 100M/; s/.*upload_max_filesize.*/upload_max_filesize = 100M/; ' "$INIFILE"

A more general script with options (for references purposes only, do not do this): #!/bin/sh test $# = 0 && exit 1 while test "$1";do case $1 in -php5) PHP=php5 ;; -sm) shift; SM=$1 ;; -rg) shift; RG=$1 ;; -pms) shift; PMS=$1 ;; -umfs) shift; UMFS=$1 ;; -mqg) shift; MQG=$1 ;; -met) shift; MET=$1 ;; -mit) shift; MIT=$1 ;; -ml) shift; ML=$1 ;; *) D=$1 ;; esac shift done test "$D" || exit 1 test -d "$HOME/$D" || exit 1 CGI="$HOME/$D/cgi-bin" mkdir -m0755 -p $CGI || exit 2 PHP=${PHP:-php} SM=${SM:-On} RG=${RG:-Off} PMS=${PMS:-8M} UMFS=${UMFS:-7M} MQG=${MQG:-Off} MET=${MET:-30} MIT=${MET:-60} ML=${ML:-8M} CGIFILE="$CGI/$PHP.cgi" INIFILE="$CGI/php.ini" echo "CGI=$CGI MQG=${MQG} UMFS=${UMFS} PMS=${PMS} RG=${RG} SM=${SM} MET=${MET}


MIT=${MIT} ML=${ML}" >&2 rsync -au /dh/cgi-system/$PHP.cgi "$CGIFILE" [ -s /etc/$PHP/cgi/php.ini ] && \ sed -e "s/^safe_mode[ ]*=.*/safe_mode = $SM/" \ -e "s/register_globals[ ]*=.*/register_globals = $RG/" \ -e "s/magic_quotes_gpc[ ]*=.*/magic_quotes_gpc = $MQG/" \ -e "s/.*post_max_size.*/post_max_size = $PMS/" \ -e "s/.*upload_max_filesize.*/upload_max_filesize = $UMFS/" \ -e "s/.*max_execution_time.*/max_execution_time = $MET/" \ -e "s/.*max_input_time.*/max_input_time = $MIT/" \ -e "s/.*memory_limit.*/memory_limit= $ML/" \ # REMOVE THE FOLLOWING LINE TO CREATE THE UPDATE-ONLY SCRIPT: /etc/$PHP/cgi/php.ini > "$INIFILE" chmod 0755 "$CGIFILE" chmod 0644 "$INIFILE" [ -s $CGI/.htaccess ] || echo "Options -Indexes" > $CGI/.htaccess touch $HOME/$D/.htaccess if grep -q '^Options' $HOME/$D/.htaccess; then grep -q '+ExecCGI' $HOME/$D/.htaccess || \ sed -i 's/^Options\(.*\)/Options\1 +ExecCGI/' $HOME/$D/.htaccess else echo "Options +ExecCGI" >> $HOME/$D/.htaccess fi grep -q '^AddHandler[ ]\+php-cgi[ ]\+.php' $HOME/$D/.htaccess || echo "AddHandler php-cgi .php" >> $HOME/$D/.htaccess if grep -q '^Action[ ]\+php-cgi' $HOME/$D/.htaccess; then sed -i "s@^Action[ ]\+php-cgi.*@Action php-cgi /cgi-bin/$PHP.cgi@" \ $HOME/$D/.htaccess else echo "Action php-cgi /cgi-bin/$PHP.cgi" >> $HOME/$D/.htaccess fi

3/ Prepare script for execution Execute the following commands into the shell. This will give you permission to execute the phpcopy.sh that we just created. chmod +x php-copy.sh

This calls our new shell script to copy the php.cgi and php.ini files into our cgi-bin directory. ./php-copy.sh

If you get the error message: "bad interpreter: No such file or directory", there is probably an unseen problem with the formatting of the file. Run the following command to convert it to proper Unix format before calling the script again: dos2unix php-copy.sh 4/ Test your new PHP setup Open one of your existing PHP pages in your browser to ensure that your newly-installed local copy of PHP is functioning properly. If there is a problem, go back over the prior steps and use your debugging skills and your mastery of PHP, shell scripts and Linux to get your newly-copied PHP interpreter working! Once everything works properly, go on to the next step. 5/ Create a shell script to make a fresh copy of php (for future use) cp php-copy.sh php-update.sh

Open the newly-created php-update.sh script in your favorite text editor and find this line: # REMOVE THE FOLLOWING LINE TO CREATE THE UPDATE-ONLY SCRIPT:

Delete that line as well as the line following it. Then save php-update.sh If you got a "bad interpreter: No such file or directory" error message when you executed ./phpcopy.sh previously, remember to convert the new file to unix format by running the following command: dos2unix php-update.sh 6/ Set up a cron task to keep php up to date Type: crontab -e

And then enter the following in the text editor that shows up (replacing 'myusername' with your specific username): @weekly /home/myusername/php-update.sh

This will update the php binary and config file once a week. 7/ Configure your website to use new the php.ini that we just set up Create the file ~/[your website directory]/.htaccess which contains the lines: Options +ExecCGI AddHandler php-cgi .php Action php-cgi /cgi-bin/php.cgi


This is telling Apache (the webserver) to use the php.cgi and php.ini that php-update.sh copied into ~/[your website directory]/cgi-bin.

How to create a custom php.ini file when nothing else works I recently needed to increase the memory_limit, upload_max_filesize and post_max_size values to something higher than the measly amount set by a particular webhost's default php.ini file. This account was set up with cPanel and overriding the php.ini defaults the usual way (by adding various lines to the .htaccess or settings.php files) didn't work at all. If you've had the same experience, you may find that this how-to works for you. This how-to is for any version of Drupal running on Linux and Apache. No Drupal modules are needed, but the devel module certainly helps.

1. Get and modify your custom php.ini file It's best to use a php.ini file that somewhat resembles the one already running on your server. You can probably find one at /usr/local/lib/php.ini or /usr/local/Zend/etc/php.ini and just copy it into your account's web folder (it may be called "public_html" or "www" or "htdocs" -- you know the one). Edit your php.ini file and save your changes. There are handbook pages on increasing memory and upload size if you need help on the syntax.

2. Creating your CGI script Now create a small script and put it in your cgi-bin directory. In your web folder, create another folder called "cgi-bin" if it's not there already. Using your preferred text editor, create a file name "php.cgi" and put the following into it: #!/bin/sh exec /usr/local/cpanel/cgi-sys/php5 -c /path/to/drupal/

If you don't have cPanel on your account, try /etc/php5 instead. In any case, replace the /path/to/drupal/ part with the full path to your Drupal installation, such as /home/youraccount/public_html/ or /home/youraccount/public_html/drupal/ Since this is a script that needs to run as an executable file, use the chmod command and type this at the command line: chmod -x php.cgi

Don't have shell access? That's outside the scope of this how-to, but you can use an FTP program that can change permissions of files, ask your webhost to make the php.cgi script executable for you, or make it executable with some PHP code (perhaps with the devel module's Execute PHP block).

3. Modifying your .htaccess file Add this one line to your .htaccess file: Action application/x-httpd-php5 /cgi-bin/php.cgi

This tells Apache to perform an action each time it encounters a file of file type "application/xhttpd-php5" -- if you're using a version of PHP older than PHP5, you'll need to change this to "application/x-httpd-php" (or upgrade to PHP5 -- ask your webhost). Finally, scroll down to the part that says "# Protect files and directories from prying eyes" and add "ini|" to the <FilesMatch> directive so it looks something like: <FilesMatch "(\.(ini|engine|inc|info|install|module|profile|po|sh|.*sql|theme|tpl(\.p hp)?|xtmpl)|code-style\.pl|Entries.*|Repository|Root|Tag|Template)$"> Order allow,deny </FilesMatch>

Notice the "ini|" on the first line? This will prevent your custom php.ini file (and anything else ending in .ini) from being accessible to the entire world. (The character after the "ini" part is a pipe, by the way, not an L.)

4. Test your site Load up your website and see if it works. If it does, good job! If it doesn't, go back to step 2 or 3 and check to see what version of PHP your site is using. You can also ask your webhost for help. If you increased your post_max_size or upload_max_size, then look at your admin/settings/uploads admin page and see how big your uploads can be. If you increased your memory_limit, check phpinfo or devel module's phpinfo() page at admin/logs/status/php


Deploy to non-writable media CD Deploy is a Webserver for Drupal that runs from a CD/DVD media on Microsoft Windows 9x/ME/XP and Vista.

HOW TO USE Structure The 'app' folder is the right place to copy your Drupal site. The 'lib' folder is used to save any 3rd party software needed to create the Webserver executable. This forder contains a Makefile which have instructions to create the folder 'server'. The 'db' folder should be created manually, containing a copy of your Drupal site's MySQL Database.

Install 3rd Party Software By default, CD Deploy comes without any 3rd party software, have to be downloaded from the Internet using the Makefile script available in folder 'lib': $ cd lib $ make all

Install Database 1. Copy Database Create the folder 'db', and copy there all files of your MySQL database. $ mkdir 'db' $ ls app db lib

LICENSE.txt

Makefile

Start.nsi

2. Register database for Drupal This might be the hardest step but is done only once: Once you have installed 3rd party software (see Install 3rd Party Software above), copy the folder 'lib/server' into a windows box. Run the program 'Start' (or Start.exe) Create directory www into server folder Start Apache and MySQL, using menu in tray icon with symbol [1] Open a web browser and go to http://localhost/apanel/phpMyAdmin/ Create a database named 'cddeploy' Stop Apache and MySQL, using menu in tray icon with symbol [1] Overwrite database 'mysql' from Windows box, it is located at usr/local/mysql/data Copy the actual files of database 'cddeploy' to 'db' folder (see Install Database, step 1)

Copy Drupal Files 1. Copy Database Create the folder 'www' into folder 'app' $ cd app $ mkdir www $ ls AUTORUN.INF

www

2. Copy your Drupal files. As reference, file index.php should appear right into folder 'www'.

Configure Settings.php Connection string of file settings.php should use following grants: user: root password: root database: cddeploy

Personalize AUTORUN Edit file AUTORUN.INF, see more about this kind of files here.

Deployment 1. Use the main Makefile $ make all

2. Burn the contents of folder 'app' to a your non-writable media (i.e: Business Card CD)

Run Test Insert your non-writable media into a Linux box and let me know the results :)

Drupal Performance Measurement & Benchmarking Benchmarking in simplistic terms is the process where you compare your current performance


with that of a better process to improve the overall standard of performance. This HOWTO sets out the methods to measure the performance of your web server for site administrators to implement changes to the server or Drupal configuration in order to produce performance improvements.

Measurement For the purposes of this HOWTO, we are focusing on the use of ab, the Apache HTTP server benchmarking tool. ab is often found pre installed with many Linux distributions, and is designed to give you an impression of how your current Apache installation performs. For Windows users, the ab tool can usually be found somewhere in the installation path of your Apache-based web server. For example, in WAMP it can be found in c:/<path to WAMP>/bin/apache/Apache<version here>/bin. Using the Windows command prompt (accessible by going to "Start", "Run" and typing "cmd"), navigate to the directory containing ab and then you can use the tool as outlined in the examples below. Alternatively, you can place the path to Apache's bin folder in your PATH variable, thus making the ab tool accessible without the need to navigate to it's directory every time you launch the command prompt. For the examples below we used the following command; ab -n 100 -c 10 http://yourserver.com/

Where; -n Number of requests to perform for the benchmarking session. (option = 100) -c Number of multiple requests to perform at a time. (option = 10)

Optimizing Drupal By default, Drupal has two settings to improve performace; Page Cache The normal cache mode is suitable for most sites and does not cause any side effects. The aggressive cache mode causes Drupal to skip the loading (init) and unloading (exit) of enabled modules when serving a cached page. This results in an additional performance boost but can cause unwanted side effects. Bandwidth optimizations - Aggregate and compress CSS files Some Drupal modules include their own CSS files. When these modules are enabled, each module's CSS file adds an additional HTTP request to the page, which can increase the load time of each page. These HTTP requests can also slightly increase server load. It is recommended to only turn this option on when your site is in production, as it can interfere with theme development.

Where to find Drupal's performace settings http://yourserver.com/admin/settings/performance

CSS Aggregation OFF, Page Cache OFF Concurrency Level: Time taken for tests: Complete requests: Failed requests: Write errors: Total transferred: HTML transferred: Requests per second: Time per request: Time per request: Transfer rate:

10 35.160824 seconds 100 0 0 1631700 bytes 1582800 bytes 2.84 [#/sec] (mean) 3516.082 [ms] (mean) 351.608 [ms] (mean, across all concurrent requests) 45.31 [Kbytes/sec] received

CSS Aggregation ON, Page Cache OFF Concurrency Level: Time taken for tests: Complete requests: Failed requests: (Connect: 0, Length: Write errors: Non-2xx responses: Total transferred: HTML transferred: Requests per second: Time per request: Time per request: Transfer rate:

10 34.796567 seconds 100 1 1, Exceptions: 0) 0 1 1469540 bytes 1420867 bytes 2.87 [#/sec] (mean) 3479.657 [ms] (mean) 347.966 [ms] (mean, across all concurrent requests) 41.24 [Kbytes/sec] received

CSS Aggregation ON, Page Cache ON Concurrency Level: Time taken for tests: Complete requests:

10 5.461753 seconds 100


Failed requests: Write errors: Total transferred: HTML transferred: Requests per second: Time per request: Time per request: Transfer rate:

0 0 1525206 bytes 1476505 bytes 18.31 [#/sec] (mean) 546.175 [ms] (mean) 54.618 [ms] (mean, across all concurrent requests) 272.62 [Kbytes/sec] received

See also Server tuning considerations

External links http://wimleers.com/article/improving-drupals-page-loading-performance http://www.mostlygeek.com/2006/08/29/how-to-make-drupal-run-85x-faster-i... http://buytaert.net/drupal-vs-joomla-performance

Drupal on OpenBSD OpenBSD is slightly different, since Apache runs in a chroot. Installation of Apache, PHP and MySQL can be taken from http://freeyourbox.org/tutorials/bsd/obsd3.8_apache_php_mysql.html. A /tmp directory must be created in the chroot for file uploads to work: # mkdir -p /var/www/tmp # chown www:daemon /var/www/tmp

Drupal with safe mode enabled and open basedir This is still undergooing some changes as I'm doing more tests... But it works on a clean drupal 4.7.3 install... I haven't tested all modules yet, so please help me out... Simply: use safe mode based on groups rather then owner and give access to my central code base for virtual hosts so that open base dir works... You need to be logged in using ssh as root... And mind you my examples are from a plesk based system... With a central code base located in /var/www/apps/drupal and the virtual hosts directory in /var/www/vhosts/{domain}.{tld}/httpdocs/ 1) create a group and a user called drupal... After -d you can put any home directory you like, but I choose the directory where my drupal code is installed... after the passwd you need to give a GOOD password for the user... groupadd drupal useradd -d /var/www/apps/drupal -g drupal drupal passwd drupal

2) Change the owner and the group of the single code base... If you don't have a single code base, only changing the group (chgrp) is enough chgrp drupal -R /var/www/apps/drupal chown drupal -R /var/www/apps/drupal

3) For the virtual hosts, change the group of the sites directory Also all directories under sites should be chmod to 2775: 2 puts all new subfiles created automatically into the group of the folder they are placed in (drupal in our case) + 7 for owner (root or drupal or ftpusername) + 7 for group (drupal) + 5 for guests If you have any more directories under sites, chmod them too chgrp drupal -R /var/www/vhosts/{domain}.{tld}/httpdocs/sites chmod 2775 /var/www/vhosts/{domain}.{tld}/httpdocs/sites chmod 2775 /var/www/vhosts/{domain}.{tld}/httpdocs/sites/default

4) Add the users to the group Plesk has a psaserv group don't worry if you don't have it... The drupal group needs the psaadm and psaftp only if you run plesk vi /etc/group psaserv:x:2523:apache,psaftp,psaadm,drupal drupal:x:10002:drupal,apache,psaftp,psaadm,{user_of_ftp_account_of_virtual_host} restart service apache

5) correct the tmp dir by creating a sub directory and putting the drupal group and user on it... mkdir /tmp/drupal chmod 2775 /tmp/drupal chgrp drupal /tmp/drupal


chgrp drupal /tmp/drupal ==> in admin change to /tmp/drupal

6) alter the code of file.inc so directories are made using 2775 rather then 775 find: @chmod($directory, 0775); replace: @chmod($directory, 02775);

Actually my version of PHP has a bug and doesn't put the 2!!! I have to remember to chmod myself then... 7) change your virtual host settings of httpd.conf In plesk this is done in: /var/www/vhosts/{domain}.{tld}/conf/vhost.conf Other systems might have another place for the conf which is the configuration for your httpd server.. With this code I have made sure that only the drupal directories benefit from group based safe mode (safe_mode_gid)... all other directories still use the default safe mode Remove the second DirectoryMatch in case you want all files and folders to benefit from group based safe mode... As I have a central code base in /var/www/apps/drupal, I need to add this dir to the open basedir... and again only for the drupal directories off course... # # allow all documents below this dir access to drupal core # <DirectoryMatch "^/var/www/vhosts/{domain}.{tld}/httpdocs"> <IfModule sapi_apache2.c> php_admin_flag safe_mode on php_admin_flag safe_mode_gid on php_admin_value open_basedir "/var/www/apps/drupal:/var/www/vhosts/{domain}.{tld}/httpdocs:/tmp" </IfModule> <IfModule mod_php5.c> php_admin_flag safe_mode on php_admin_flag safe_mode_gid on php_admin_value open_basedir "/var/www/apps/drupal:/var/www/vhosts/{domain}.{tld}/httpdocs:/tmp" </IfModule> </DirectoryMatch> # # now limit for all directories below root that are not includes or modules # users want other apps installed too... so no access to drupal # <DirectoryMatch "^(/var/www/vhosts/{domain}.{tld}/httpdocs/)(?!includes/|modules/).*/"> <IfModule sapi_apache2.c> php_admin_flag safe_mode on php_admin_flag safe_mode_gid off php_admin_value open_basedir "/var/www/vhosts/{domain}.{tld}/httpdocs:/tmp" </IfModule> <IfModule mod_php5.c> php_admin_flag safe_mode on php_admin_flag safe_mode_gid off php_admin_value open_basedir "/var/www/vhosts/{domain}.{tld}/httpdocs:/tmp" </IfModule> </DirectoryMatch>

How to degrade your Drupal db from MySQL 4.1.X/5.0.X to MySQL 4.0.X I came across this issue when was developing a site for a client. I have MySQL 5.0.X at my localhost and staging host but client's hosting still uses MySQL 4.0.X. So here is the solution: 0. Always make backup first. 1. Drop tables "accesslog", "cache" and "watchdog" (DROP TABLE accesslog, cache, watchdog;)

2. Recreate tables from Drupal mysql dump for v4.0 CREATE TABLE accesslog ( aid int(10) NOT NULL auto_increment, sid varchar(32) NOT NULL default '', title varchar(255) default NULL, path varchar(255) default NULL, url varchar(255) default NULL, hostname varchar(128) default NULL, uid int(10) unsigned default '0', timer int(10) unsigned NOT NULL default '0', timestamp int(11) unsigned NOT NULL default '0', KEY accesslog_timestamp (timestamp), PRIMARY KEY (aid) );


CREATE TABLE cache ( cid varchar(255) NOT NULL default '', data longblob, expire int(11) NOT NULL default '0', created int(11) NOT NULL default '0', headers text, PRIMARY KEY (cid), INDEX expire (expire) ); CREATE TABLE watchdog ( wid int(5) NOT NULL auto_increment, uid int(10) NOT NULL default '0', type varchar(16) NOT NULL default '', message longtext NOT NULL, severity tinyint(3) unsigned NOT NULL default '0', link varchar(255) NOT NULL default '', location varchar(128) NOT NULL default '', referer varchar(128) NOT NULL default '', hostname varchar(128) NOT NULL default '', timestamp int(11) NOT NULL default '0', PRIMARY KEY (wid) );

3. Make a dump for MySQL 4.0.X mysqldump -u root -p -h localhost --compatible=mysql40 mydatabase > mydatabase.sql

4. Upload this dump to your MySQL 4.0.X server. 5. Refresh your browser a couple of times. Enjoy!

How-To: Virtual Hosting with Drupal Introduction This book explaines how I've enabled Drupal hosting for my customers. I hope it is helpfull for other Drupal hosters...

Environment this is the environment used to setup my drupal hosting linux RHEL 4 plesk 8.0.1 php 4.3.9 mysql 4.1.12 apache 2.0.52 drupal 4.7.x

Requirements keep safe mode on at all times without the need for chmod 777 keep base dir restriction on at all times integrate into plesk so that backup, databases, quotas, etc keeps working drupal must run as managed application updates done once for all vhosts :: use single code base approved modules and themes available for vhosts to select from pre-activate drupal modules that enhance basic content management: SEO, security, ... eaccelerator must speed up php processing efficiently :: use single code base Each individual vhost should be able to use its own themes and modules should be accessible trough different URLs :: drupal multisite option should also be able to use other applications then drupal within the same site should experience drupal no different as if we would install drupal for each vhost seperatly Enhance security if needed on the drupal package Single code base should be included in the normal backup mechanism available in plesk

Next pages As the book seems to order the sub pages by date, I list here the order of further reading: 1. Solution overview: http://drupal.org/node/90144 2. Prepare environment: http://drupal.org/node/90207

How-To: Virtual Hosting with Drupal ::


How-To: Virtual Hosting with Drupal :: Prepare environment Introduction On this page I will explain some preparations of the environment like creating the drupal group, a tempory files location, application reference location, etc. Please read previous pages: Requirements: http://drupal.org/node/80472 Solution overview: http://drupal.org/node/90144

The drupal group As we will enable the safe mode in group mode, we are in need of a system group. SSH into the server as SU (super user or root) add the group $ groupadd drupal

add the apache, psaftp and psaadm users to the group the group id (10002) could be different $ vi /etc/group drupal:x:10002:apache,psaftp,psaadm

The temp directory Also within the temp directory we need a matching group because uploaded files are temporarly saved there and then copied to the final destination. Therefore we will create a seperate directory and chmod it so that each file created their automatically receives the drupal group. The latter is achieved by addin 2000 to the chmod command. $ mkdir /tmp/drupal $ chmod 2775 /tmp/drupal $ chgrp drupal /tmp/drupal

You will see an s appear instead of the x at the group part: drwxrwsr-x

2 root

drupal

4096 Oct 16 15:09 drupal

The reference location This is a directory where all symlinks are placed that point to the current version of an application. It is this location that all vhosts will reference so that they don't depend on the current version. When switching versions, we only need to alter the symlinks at this location and all vhosts will be updated automatically. See further pages for more info. SSH into the server as SU (super user or root) $ mkdir /var/www/apps

Navigation As the book seems to order the sub pages by date, I list here the order of further reading: Previous: Solution Overview: http://drupal.org/node/90144 Next: Single code base: Comming soon...

How-To: Virtual Hosting with Drupal :: Solution Overview Introduction On this page I will explain in general what solutions are provided for the different requirements Please read previous Requirements page: http://drupal.org/node/80472

Safe mode Enabling Safe Mode imposes several restrictions on PHP scripts. These restrictions are mostly concerned with file access, access to environment variables and controlling the execution of external processes. There are problems which may be encountered when this mechanism is turned on, notably when attempting to work with files owned by different users and files which have been created at runtime by the script (which will be owned by the owner of the web server process). In Plesk (and most other virtual hosts system) each vhost gets its own owner (FTP user) that is set


when files are uploaded to the server. As we want a single code base located in a central place, the owner of the files within that central place will be different then the owner of the files in the vhost's location. In order to work around these issues, a relaxed form of the file permission checking is also provided by Safe Mode. Using the php.ini directive safe_mode_gid, it is possible to relax the user ID check to a group ID check. That is, if the script has the same group ID as the file on which a file operation was requested, the operation will succeed. If the script owners and the web server are members of the same group, and all hosted files are owned by this group, the file operations will succeed regardless of user ID. Thus, we will create a group "drupal" that will cover all drupal related files across vhosts and enable the safe_mode_gid. By doing so we will still have a more secure system then when we would disable the safe mode.

Information taken from here: http://www.acunetix.com/websitesecurity/php-security-5.htm

Open Basedir Open Basedir limits the files that can be opened by PHP to the specified directory-tree, including the file itself. This directive is NOT affected by whether Safe Mode is turned On or Off. When a script tries to open a file with, for example, fopen() or gzopen(), the location of the file is checked. When the file is outside the specified directory-tree, PHP will refuse to open it. All symbolic links are resolved, so it's not possible to avoid this restriction with a symlink. In Plesk (and most other virtual hosts system) each vhost gets its own open_basedir restriction that restricts the opening of files to the httpdocs directory and the /tmp directory. As we want a single code base located in a central place, that location will not be accepted trough this restriction. Thus, we will alter the open basedir restriction so that it includes the single code base location. But as we want other applications to be installed within the same vhost, we will add the location only for directories using drupal.

Plesk integration The plesk control panel works great as long as you keep the directory structure and security as it is provided by plesk. Therefore we need to merge the single code base and each vhost within the provided structure. The single code base will be installed as a normal vhost but limiting web access to the domain. As such a backup of the single code base can be scheduled and FTP can be enabled to upload new modules and themes. Each vhost will have his own local "sites" directory and will create its database trough the plesk interface.

Managed application As the single code base resides as a single vhost, we can upgrade the code, add modules and themes into one location. To make upgrading easier we need to make sure that each vhost that uses drupal, references one single point independent of the drupal version. We will make subdirectories per drupal version in the single code base location and have one single reference that we can switch when going to a new version. Thus their will be a symbolic link called "drupal" pointing towards a drupal version "drupal-4.7.3" and each vhost that uses drupal will have symbolic links pointing towards the "drupal" symbolic link. When we change version, we only need to change the "drupal" symbolic link once and all vhosts will be adjusted accordingly.

eAccelerator The eAccelerator speeds up the execution of the php script by precompiling them and putting them into memory. The disadvantage is that you need to have the memory and the more you have the more php files can be kept for fast execution. Therefore we need to limit the number of php files on the server. By using the single code base, every vhosts uses the same files and thus memory usage can be limited and all php scripts run faster.

more on eAccelerator can be found here: http://eaccelerator.net

Each individual vhost requirements as noted above in the "Plesk Integration" part, each vhost gets his own "sites" directory that enables them to add their own modules and themes. Also as natively available by drupal, the multisites mechanism will work for the same reason. Using symbolic links and this "sites" directory will give the user the feeling that it is a full drupal install on their system. The local "sites" directory will allow the flexibility required

Navigation


As the book seems to order the sub pages by date, I list here the order of further reading: Previous: Requirements: http://drupal.org/node/80472 Next: Prepare environment: http://drupal.org/node/90207

Installing Drupal behind an Actiontec GT701WG router The popular Actiontec GT701-WG (and possibly other routers) DSL modem/router has a peculiar behavior that affects how base_url must be set up in sites/default/settings.php. The solution is to replace the PHP code that sets base_url with some code that is available below. Here's the scenario: you are using computer A to access and use your Drupal site, computer B is the server that is hosting the Drupal site, and both computers are plugged into a GT701 router, which in turn is linked to the Internet. Internet users can access your server from the outside by typing in www.example.com (because you set up port forwarding on the GT701). However, the GT701 will not allow you to type that address in from computer A, because the GT701 interprets such an action as a request to view the GT701'a administrative interface. Instead, you must type in the local network IP address (usually 192.168.0.xxx) of computer B to access and use your Drupal site. However, this causes a problem because base_url is used by Drupal to create all the site's links. If base_url is set for www.example.com, you will be unable to use the site from computer A (even if you type in the local IP). If base_url is set for the local network IP address, Internet users will not be able to use the site. In other words, base_url has to be different for internal network users (computer A) and Internet users. To do this, replace the line $base_url = "http://www.example.com"; with the following PHP code: <?php $ipaddress = getenv(REMOTE_ADDR); $local= strpos($ipaddress, '192.168.'); if ( $local=== false ) { $base_url = "http://www.example.com"; } else { $base_url = "local network IP address"; } ?>

Here, replace www.example.com with the actual URL and local network IP address with the proper IP address (something like 192.168.xxx.xxx). It is possible that your local network IP addresses do not begin with 192.168; in that case, you would need to change the code to look for the local IP range accordingly. With this setup, you can access the Drupal site using computer A by typing in Computer B's local IP address in your browser, while Internet users can continue to access it by typing in www.example.com

More than one Drupal site on one machine There are several possible configurations for running multiple Drupal servers on the same hardware. You can separate them by directories or by vhosts, they can share configurations or split them or, in some cases, have a mixture, but all of these methods have at their heart the ./sites/domain_or_host_name/settings.php configuration file and the search-sequence where the Drupal program will search first for a configuration named for the current page and then to the current host before settling for the default.

General rules for multiple Drupal deployments Each of the possible multi-drupal scenarios is discussed in more detail in the sections that follow, but the general form for the alternate configuration filename is: ./sites/vhost.uri/settings.php

Note how the path separator ('/') must be changed to a dot. As an example, the vhost drupal.mysite.net may have one primary drupal server at the DOCUMENT_ROOT location, but a second site may begin at DOCUMENT_ROOT/altserver. For this case, the configuration file would be ./sites/drupal.mysite.net.altserver/settings.php Note that in the case of having a separate site in a subdirectory such as DOCUMENT_ROOT/altserver in addition to the site configuration directory it is necessary to create a symlink from the subdirectory to the parent directory. In this case linking the DOCUMENT_ROOT directory to be called altserver. On IIS because symbolic links are not available it is necessary to create a virtual directory for /altserver in the IIS configuration. Within that configuration file, the most common and minimal option is to set the $db_url that specifies the host, database and login for the Drupal tables, as well as the $base_url. But you can also include assignments to override anything in the VARIABLES table. This allows you to redefine


the theme, the site footer and contact email, blocks per-page limits, even the name you use for anonymous. Drupal IDs: When using multiple drupal servers on the same hardware, each new configuration will result in a new host component for the username@host Drupal login ID (used when logging into a foreign Drupal server). For example, if you have a directory partitioned host at drupal.mysite.net/altserver your usename to login to some other Drupal server would be USENAME@drupal.mysite.net/altserver. Prefixing Database Tables to put them in One Database If you only have one database then it is necessary to use database table prefixing. See this handbook page for details on how to achieve that.

PCRE_UTF8 solution for VPS servers | FreeBSD installation PROBLEMS? - PCRE_UTF8 support on VPS server accounts? Look here! I receive the same error on every page with a basic install however everything seems to be functioning otherwise fine. warning: preg_match(): Compilation failed: this version of PCRE is not compiled with PCRE_UTF8 support at offset 0 in /home/brandform/www/drupal.brandform.net/includes/unicode.inc on line 32.

The solution was much more difficult to find than i had hoped so maybe this post will be useful to some others as well. I had to uninstall the pcre package and reinstall the proper one with utf-8. heres the procedure: login to server via shell, on VPS2 accounts, # su root

then type password basically you want to be root user. then type: # pkg_info

which will show you the installed packages. type: # pkg_delete pcre-6.4

for me it was 6.4 but yours may be different. make sure to enter the proper version of pcre displayed in pkg_info. then you want to change to the directory where your ports collection is. on FreeBSD it is: /ports/devel/pcre-utf8 so type into your shell prompt: # # # # #

cd / cd ports/devel/pcre-utf8 make make install make clean

then youll want to restart the apache server... something like this: # restart_apache

thats it... fixed everything for me. after over a day of pulling my hair out and almost abandoning the effort......

Known causes of PCRE server errors In the event that you see the following error: warning: preg_match: internal pcre_fullinfo() error -3 in /SERVER_PATH/includes/common.inc on line 592.

Be aware that this issue exists at the server level, outside of Drupal code. Put plainly, the error means that the server's PCRE (Perl Compatible Regular Expression) library is not configured correctly. This error may be caused any time a Drupal module calls a PCRE function. Typically, the error appears within common.inc, but this is not always the case. Depending on the server environment, there are several different causes (including a reported bug in some versions of PHP 5). This manual page is designed to capture known instances of this bug, Known Causes An upgrade of RedHat included a bad library and required a patch. (Details to follow)


Recommendations Send the content of the error to your system admin and have them debug your PHP's PCRE module.

Redirecting specific pages to new URLs (301 redirects in Drupal) If you are porting over an existing website to Drupal, one consideration is how you redirect the old page URLs to the appropriate pages on the Drupal version of the site. If you don't want to create custom rewrite paths within Drupal for those nodes -- or perhaps cannot due to clean URLs or filename suffixes -- then 301 redirects are considered the best way to handle redirected pages, for they inform search engines to update their databases with the new paths. This way, you should not risk your search engine pagerank or lose site visitors with 404 "not found" errors. However, 301 redirects cannot be done using the common approach. Yet establishing 301 redirects is quite easy, provided you have mod_rewrite enabled in your .htaccess file.

How to create 301 redirects in Drupal Apache mod_rewrite Edit your .htaccess file in a text editor. [Note: Be sure to save the file in "UTF-8" format.] In the file, you will find the commands: # Various rewrite rules. <IfModule mod_rewrite.c> RewriteEngine on # Modify the RewriteBase if you are using Drupal in a subdirectory and # the rewrite rules are not working properly. #RewriteBase /drupal RewriteBase /

Immediately after that code -- and before the Drupal-provided "Rewrite old-style URLs" commands -- add your rewrite rules using the following format: #custom redirects RewriteRule ^old/URL/path$ http://example.com/new/path [R=301,L] #end custom redirects

Note the convention: The old path is simply the path off the root. The new path is the full path, including the domain. The [R=301,L] code is the 301 redirect instruction. (axbom notes: "The 301 tells browsers and spiders it is a permanent redirect, and the L ensures that no other rewrites are processed on the URL before it reaches Drupal; Hence place this code above Drupal 's own URL rewrite, but below the command RewriteEngine on.") If you have more paths to add, insert the rewrite commands as their own line as above. For more information, see the forum thread at http://drupal.org/node/16084 [from where I drew this information]

Migrating the Drupal way...saving those old URLs Mapping a previously used ID to a NID Let's say that the old URL looked like http://example.com/index.asp?id=123, but your new Drupal site uses a URL like http://example.com/node/123. The following rules could be used to remap the old URL unbeknownst to the user. The following examples would be placed before the Drupal rewrite rules in your .htaccess file: # Match a request for index.asp RewriteCond %{REQUEST_URI} ^/index.asp$ # Match a query string like id=[some number] and capture that number RewriteCond %{QUERY_STRING} ^id=([0-9]*)$ # Rewrite a Drupal friendly URL using the captured number # Note that %1 is a backreference from a RewriteCond # where $1 is a backreference from a RewriteRule RewriteRule ^.*$ index.php?q=node/%1 [L]

Mapping a previously used numeric file name to a NID Perhaps your former CMS creates static HTML files, but uses an ID predictably in the file name. The following example will capture any numeric characters in parentheses and append them to index.php?q=node/. e.g. requesting file5.html would return the same contents as node/5. # Map a filename with a predictable number to a drupal nid RewriteRule ^file([0-9]*)\.html$ index.php?q=node/$1 [L]

Redirecting as a solution


If you want to send your visitors to the correct page and use your new Drupal-style URLs, you can use a permanent redirect. The 301 HTTP status code tells your visitors that the old URL has a new permanent home. Search engines should also respect a 301 and index appropriately. Using the RewriteRules above, you could accomplish this by using the flags [R=301,L], demonstrated below: # Match a request for index.asp RewriteCond %{REQUEST_URI} ^/index.asp$ # Match a query string like id=[some number] and capture that number RewriteCond %{QUERY_STRING} ^id=([0-9]*)$ # Permanently redirect - note the ? at the end of the address # which is necessary to not append the original query string RewriteRule ^.*$ http://example.com/node/%1? [R=301,L]

The above example will redirect the user to a URL like /node/123. So, what if you want to permanently redirect to a more friendly URL alias? A great option is to use the Global Redirect module for Drupal. Global Redirect can also be used in conjunction with mod_rewrite rules. (The first set of mod_rewrite rules mentioned in this post are a good example) Assume that you have a URL alias like /your-aliased-url used for /node/123. Using mod_rewrite, you can map /index.asp?id=123 to /node/123 and Global Redirect will permanently redirect to your alias. This effectively sends all requests for /index.asp?id=123 to /your-aliased-url. sourcehttp://acquia.com/blog/migrating-drupal-way-part-ii-saving-those-old-urls

Running Drupal on Ubuntu Server Learn how to set up Ubuntu with Apache, MySQL, SSH Server, PhpMyAdmin, and configure all with some minor tweaks that will cause a major performance boost over a barebones install. Setting up Ubuntu Server for hosting Drupal sites

Showing the public a holding page while you develop or troubleshoot your site While you develop your Drupal site you may want to show the public either a simple holding page or keep an older version of the site operational. You can either develop Drupal in a subdirectory while keeping the site in place, or you can develop Drupal in the webroot and put the holding page or old site in a subdirectory. This page considers the latter approach. This technique can also be used to display a more friendly message than the standard 'Can't connect to the database' message when your site goes down. For a module-based approach, see http://drupal.org/project/holding.

Instructions Create your static holding page(s) and place it in a subdirectory called, for example, 'holding'. Then add the following lines to your Drupal installation's .htaccess file, below the 'RewriteEngine on' line: ##### rewriting for holding page RewriteCond %{HTTP_HOST} mysite\.com [NC] RewriteCond %{REQUEST_URI} !^/holding [NC] RewriteCond %{HTTP_HOST} !^drupal [NC] RewriteRule ^(.*)$ /holding/$1 [L] ##### end

Replace 'mysite\.com' with the domain name of your own site, escaping the full stops with backslashes, as in the example. This will redirect visitors of 'mysite.com' to 'mysite.com/holding', without them realising. To access your Drupal site, you need an alternative domain name. There are two ways to do this. The easiest method is if you have the original domain name for your hosting, such as 'mysite.somehostingcompany.com'. Using this to reach your site will give you the Drupal site rather than the holding site. You may also be able to access your site at your IP like this: http://1.1.1.1/~username Alternatively, create a subdomain called 'drupal' for your site, set to the same webroot as the main domain. The third line in the code snippet above means that visiting 'drupal.mysite.com' won't be redirected to the holding page. If you get a 403 for the holding site, you'll need to put an .htaccess file in the holding folder, as Drupal's .htaccess only allows index.php as a directory listing. This is all you need for the holding/.htaccess file: # Set the default handler. DirectoryIndex index.php index.html


Transforming a default table names installation into a prefix table names installation This operation may be useful in many cases, expecially if you need to move your site from an host providing a dedicated drupal installation database to a shared database with all data in one database. 1) Log off your Drupal site, back up the database, the risk breaking your data is definitely here. 2) Export the database to text file. You can use i.e. phpMyAdmin Export function with the following options: "SQL" (as format to use). Be sure to check Structure (and "Add AUTO_INCREMENT value" in it) and Data (and "Use exadecimal for binary fields"). Don't use compression settings, you want a plain file. 3) Decide the prefix for the tables. I.e. all tables name changed from "xxxxx" to "dpl_xxxxx". 4) Open the text dump created, search and replace every CREATE TABLE '

with CREATE TABLE 'dpl_

and INSERT INTO '

with INSERT INTO 'dpl_

where dpl_ is any SQL valid prefix you want to use. 5) Save the dump 6) Drop the current database (if you are reinstalling on same machine, else skip) 7) Create the database in the destination machine. I give "utf8_unicode_ci" as default collation. From now on everything is done on the destination computer. 8) Import the saved dump there. 9) Open a SQL prompt / window / whatever (i.e. "SQL" option in phpMyAdmin). 10) At the SQL prompt enter: update dpl_sequences set name = concat('dpl_', name)

Of course if you use something different than 'dpl_', you have to apply such prefix both in front of "sequences" and in the concat operator. Example, if you used "my_stuff_" it'd become: update my_stuff_sequences set name = concat('my_stuff_', name)

Beware, the prefix must be the same in the whole process or everything will break. I'd really suggest keeping case consistency too. 11) Open the settings.php, usually it'll reside in the /sites/default folder. If you have a multi-site installation you have to find the relevant php settings file in the affected domain folder. 12) If you changed database name / server, search for: $db_url = 'server://path/database_name';

and alter it as per the instructions reported above that line. 13) Search for: $db_prefix = '';

(should be right next of the above $db_url statement) and put the new prefix inside the ' '. Example: $db_prefix = 'dpl_';

14) Launch the home page of the destination installation. If everything shows up, you are at a good point. Else you have to double check what step did you miss. 15) Log in as administrator and create a quick page and a story. If you see green and encouraging "page / story added" you are done. If you get "Warning: duplicate key / ID" whatever, you skipped something about prefixes and you have to drop the database and redo from point 6.

Note


Amazing tutorial! it works for Drupal 6.x but in my case I also had to replace: LOCK TABLES ' and ALTER TABLE ' with LOCK TABLES 'pre_ and ALTER TABLE 'pre_

Use Microsoft Access to query your drupal database Hope this helps someone. I used ODBC to MySQL over SSH port fowarding on a Windows client. Here goes: 1) Download and install cygwin (www.cygwin.com) on your windows machine; make sure to include OpenSSH, tcp_wrappers, and zlib. There’s a nice set of instructions here: http://pigtail.net/LRP/printsrv/cygwin-sshd.html. NOTE: You can also use Plink. 2) Download and install MySQL Connector / ODBC 3.51 on your machine, located here: http://dev.mysql.com/downloads/connector/odbc/3.51.html. I downloaded the Windows Zip/Setup.EXE 3) I needed to comment out the line “skip networking” in /etc/my.cnf. I did this by 1) SSH into my server, 2) type cd /etc and press enter, 3) type pico my.cnf and press enter, 4) put a “#” in front of the phrase “skip networking” 5) Save 6) Restart MySQL server. 4) Now you’re ready to set up an SSH tunnel from your local machine to the host. Open Cygwin and use the following command: ssh -N -f -L 3307:localhost:3306 user@example.com. 5) Setup the ODBC connection. In Vista, goto Control Panel -> Administrative Tools -> Data sources and click Add. Select MySQL ODBC 3.51 Driver, click finish, and fill in the fields like such: Data source name: You pick! Description: You pick Server: localhost User: the name that you use to login to your server Password: ******** Note: Your password needs to be 8 characters or less. Database: the name of the database to connect to. On the 2nd page of the dialog, enter port 3307 Click Test and you should be able to connect! 6) To connect MS Access to your database(s), goto external data -> import from ODBC, and when it says select data source goto machine data source. You should see your data source name. Click on that and follow the prompts. You’re good to go!

Using Apache .htaccess files to stop proxies from caching pages There are a number of situations that might result in visitors accessing a Drupal site via a caching web proxy. Even though such caches are typically a good thing for sites, sometimes these caching schemes can have undesirable side effects and you may find that instructing proxy servers not to cache pages is the only solution to some problems. (You can learn more about web caches and how they work.) Some problems that caches may cause with Drupal sites include: A change that a user made to a page may not be reflected immediately, forcing the user to wait some time to see content they contributed (such as a comment) appear on the site. User identities become confused. A user may login under their username but when they access another page, the proxy server sends the cached page from another user's session, effectively switching the identities of the user. Proxy servers can be instructed to stop caching pages for a Drupal site using a variety of techniques. The simplest involves modifying your theme to include certain <meta … /> tags in your theme's <head> section. Although using these tags may be more convenient, many caching proxies won't respond to their instructions. In these cases, you must embed cache controls in the HTTP headers sent by the web server itself. Apache web servers typically use mod_expires to configure and send these cache-controlling HTTP headers. Assuming that mod_expires is enabled on your web server, try adding the following lines to your .htaccess file to stop the proxy server from caching Drupal pages: ExpiresActive on ExpiresDefault "access plus 0 seconds"

Alternatively, if the web server has both mod_expires and mod_headers this snippet may be more robust. ExpiresDefault A0 Header set Cache-Control "no-store, no-cache, must-revalidate, max-age=0" Header set Pragma "no-cache"

Note that Drupal's internal cache is distinct from any caching that web servers may do. For more information about Drupal's internal caching mechanism, see Cache support. For information


regarding how to enable a web cache of your pages, see Squid Caching.

Increase PHP memory limit (4 methods) While Drupal core will run with 8 MB of memory configured for your server, you may need to increase this depending on how may modules you use on your site. In Drupal 4.7.x and earlier, when you go into ?q=admin/modules, you may experience a blank screen. This is caused by Drupal loading all the modules of your site into memory, whether you have them turned on or not. If you get a blank screen here, you have two choices; increase your allocated memory for PHP or delete unused modules. In Drupal 5.x and above, this problem has been fixed, and the modules page no longer loads all modules. If you are still getting out of memory problems, you should either reduce the number of modules used or increase your allocated memory for PHP. Depending on your host, this can be done in a number of places with the most likely being php.ini or .htaccess depending on your hosting situation. Add for example: memory_limit = 16M

to your php.ini file (recommended, if you have access)

With root access, you can use the sed util in Linux/Unix based systems, in order to increase the memory for 64M. Don't forget to properly locate your php.ini file! sed -i 's/memory_limit = .*/memory_limit = 64M/' /etc/php5/apache2/php.ini ini_set('memory_limit', '16M'); to your sites/default/settings.php file php_value memory_limit 16M to your .htaccess file in the Drupal root

Or simply install: http://drupal.org/project/drupal_tweaks and increase your PHP memory limit in settings. You will need to experiment with the value that is right for you depending on which modules you are using. Some people find they need to set the memory to 24M or 32MB or higher (e.g. 96MB is recommended for a site with built-in image processing using ImageAPI GD). NOTE: Some hosts do not allow you to control how much PHP memory is available. In this case you will need to work with your host, be very conservative with your module selection and testing or look for a new host that allows more flexibility.

Installing modules and themes Now that you've installed Drupal, you will want to customize it to your tastes by adding modules and themes. The basics of managing modules and themes are fairly similar. If you browse to the sites/all folder you will find a README.txt file. This directory should be used to place downloaded and custom modules and themes which are common to all sites. This will allow you to more easily update Drupal core files. These modules and themes should be placed in subdirectories called modules and themes as follows: sites/all/modules sites/all/themes

Installing contributed modules You can add third-party contributed modules to extend or alter Drupal's behavior. Basic instructions Obtain the module as an archive and extract the files to your Drupal installation (normally into sites/all/modules) , read the directions, and enable the module in Administer > Site building > Modules. Note that experienced Drupal site builders generally use command-line techniques like the UNIX wget command or drush. There is also a Plug-in Manager module that allows you to install other modules using your Drupal site's web interface.. Detailed Instructions 1. Download the module. The module version must be compatible with your version of Drupal. Note that "Development snapshots" are modules that are in an active stage of development. They may be written for a previous/current/future version of Drupal, and they are considered unstable and should be handled with care. 2. Extract the files. When you first get the module, it will appear in a compressed file format such as 'tar.gz'. On Windows, use a program like 7-zip to extract it. On the Mac, you can use Stuffit Expander. For *nix systems, use the command line: tar -zxvf modulename-drupalversionnumber.tar.gz You should see a list of files extracted into a folder.


3. Upload the folder. FTP your files to the desired modules folder in your Drupal installation. The modules folder at the top level of your Drupal installation is reserved for Drupal core modules (the ones that come with the original download of Drupal). So, you should generally create a sites/all/modules/ directory and put uploaded modules there. If you are running a multi-site installation of Drupal, you can create a modules folder under sites/my.site.folder and put modules there that are specific to a particular site in your installation. Modules that will be shared between all sites should be placed in sites/all/modules. 4. Read the directions. If the module has an installation file (usually INSTALL.txt and/or README.txt), read it for specific instructions. There are modules that require special treatment, and even modules that depend on other downloaded files to function properly. Sometimes the README file has no .txt extension. When you try to double-click on it, your computer doesn't know what program to use. In that case, open your favorite text editor first, and then open the file using the editor's 'file open' command. 5. Enable the module. Navigate to Administer > Site building > Modules. Check the 'Enabled' box next to the module and then click the 'Save Configuration' button at the bottom. NOTE: If you're upgrading an existing module you'll need to browse to your update page at www.example.com/update.php and click on 'run the database upgrade script'. 6. Set up permissions. Some modules will require you change permissions to get them working. Permissions information may be in the instructions that came with the module. Usually, go to Administer > User management > Permissions (for Drupal 5 it's Administer > User management > Access control). Scroll down to see if the module appears in the list and, if it does, give the appropriate permissions to desired roles. 7. Adjust settings. Most modules will have some type of settings page. It will vary from module to module but but if not described in the README.txt file it will usually be located under Administer > Site building or Administer > Site configuration. If you have trouble locating a module's settings page try navigating to "admin/by-module" and see if the module appears in the list. If it does, it's settings page(s) will be listed also. If all else fails, check the module's .module file for a 'modulename_menu' function-- even if you're not a coder the settings path, if there is one, should be pretty easy to discern. 8. If you run into problems, search the module's issue queue and the forums. If your problem hasn't already been addressed, post a question or issue and someone will try to help you out. Note: To keep up-to-date on any issues and fixes related to your newly installed module(s), you can create a user account (if you haven't done so all ready) and then subscribe to each module you are using. Note: You can only have one copy of a module with the same name in each Drupal site. The module's name is determined by the name of the .module file, not by the name of the directory.

Installing themes 1. Download the theme. Make sure the version of the theme matches your version of Drupal. Note that themes labeled "CVS" or "DEV" are in a development stage. They may be written for a previous/current/future version of Drupal, and they are considered unstable and should be handled with care. 2. Extract the files. When you first get the theme, it will appear in a compressed file format such as 'tar.gz'. On Windows, use a program like 7-Zip to extract it. On the Mac, you can use Stuffit Expander. To extract the file using the Unix command line: tar -zxvf themename-drupalversionnumber.tar.gz

You should see a list of files extracted into a folder. 3. Upload the folder. FTP/Copy/SCP your files to the desired themes folder in your Drupal installation. Since the themes folder at the top level of Drupal is typically reserved for Drupal core themes, you should create a sites/all/themes/ directory and put uploaded themes there. If you are running a multi-site installation of Drupal, you can create a themes folder under sites/my.site.folder and put themes there that are specific to a particular site in your installation. Themes that will be shared between all sites should be placed in sites/all/themes. 4. Read the directions. If the theme has an installation file (usually INSTALL.txt and/or README.txt), read it for specific instructions. There are themes that require special treatment to function properly. 5. Enable the theme. Go to administer > site building > themes. Check the 'Enabled' box next to the theme and then click the 'Save Configuration' button at the bottom. If you run into problems, check the themes issue queue and search the forums. If your problem hasn't already been addressed, post a question and someone will try to help you out.

Moving modules and themes The best practice is to keep all of your contributed modules and themes in the sites/all/modules or sites/all/themes directory, as appropriate. If you are upgrading from a previous version or have already installed them in the main modules or themes directories and you wish to move them, it is possible but you just need to make sure Drupal knows you moved them. 1. Go to Administer > Site Building > Modules (or Themes) and disable the modules/themes you wish to move. 2. Move the modules/themes to the new directory you wish them to live in. 3. Now go back and enable the modules again. Drupal will locate them in the new directory and update the system table as needed.


The system table gets rebuilt when you visit: admin/build/modules. So if your module has moved and you've forgotten to disable it then just visit the modules page and you should be fine. Typically you'll see a PHP "Fatal error: Call to undefined function myfunction()" error when Drupal doesn't know where your module is. This is not necessarily true as of Drupal 6. Some contributed modules will NOT come back online because of paths stored in their settings and may cause various database errors. Always remember to make a complete backup first. In these cases, try uninstalling the module completely. Or just leave it where it was before you moved it. It's also possible to modify the database directly to change the path if necessary.

Uninstalling modules Uninstalling a module is easy! Remember to create a backup of your database first. Before deleting the files, it must be disabled. Disabling a module Go to example.com/admin/build/modules.

Scroll down until you find the modules that you'd like to remove.

In this case, you'll want to disable all of the “Views” set of modules. In order to disable a module, click to uncheck the “enabled” checkbox that's next to it. If a module box is greyed out, that means you cannot yet disable it. This is because there is another module that requires that module to be active.

In this case, Views RSS and Views UI must be disabled first before the Views module can be disabled. Uncheck those boxes first, then click on “Save configuration”


Once the page refreshes, the checkbox next to the Views module will no longer be greyed out. Repeat the process with the Views module, and everything will be disabled.

Uninstalling a module Not all modules have specific uninstall functions programmed in. A module will only show up on the uninstall tab if it has this feature. If it doesn't, skip this step and simply delete its files. Click on the “uninstall� tab at the top of the example.com/admin/build/modules page. It will look like this:

Click on the checkbox next to the desired module (in this case Views) and click the uninstall button.

Next, you'll have a screen verifying your intention to uninstall this module.

Click on the uninstall button again, and you'll get a screen with green text verifying that the module is uninstalled. Remove/delete module files Removal of module files is similar to the process of uploading module files. 1. Use the same FTP client software as used in the uploading process, and use the same connection information. 2. Navigate to the same directory 3. Instead of uploading files from your own computer, delete the directory containing the desired module.


SSH 1. Use the same SSH client software as used in the uploading process, and use the same connection information. 2. Navigate to the same directory. 3. Instead of using the wget command to upload files, use the command “ rm -rf [module folder name]”. In the Views example, you would type “ rm -rf views”. Note: a module without an uninstaller may leave tables or fields in your database. A module like this must be dealt with manually in order to completely remove it from your database, which is best practice to keep your site clean if you definitely do not want to use this module again. There may be an obviously named table or set of tables in your database which can be easily deleted, but the only way to know for sure is to examine the module installation file to see what was added in the first place. Always create a backup snapshot of your database before attempting this! From TopNotchThemes.com

Upgrading modules Upgrading modules is a more involved process than installing or removing modules. The steps should be followed closely, as all these steps are necessary to ensure the stability of your website. Backup your Files and Database Your website's database contains all of its content, as well as all of its settings and configuration. As such, any operation which modifies it could, although unlikely, be potentially damaging. Backing up your website's files will ensure that you can revert back to the point when things were working. We highly recommend you take steps to back it up before performing this procedure. More information about taking backups may be found at http://drupal.org/node/250790. Update Module Update capabilities are not automatically available in Drupal. To keep track of updates to your installed modules, you may want to install the Update module, which is located here: http://drupal.org/project/update_status Check Module status The Update module can check your modules for updated status automatically to assist you with updates. It will provide you with download links and available versions. Once the Upgrade module is installed and enabled, you can reach a list of modules for update by either clicking on the “available updates” link on the main administration page or by going directly to example.com/admin/logs/update. This page checks the current and the installed versions of modules, and will give a report on their status. This will allow easy updates without having to manually check each module installed in your website.

In this example, the Views module is out of date. Any out-of-date module will be labeled in red, as shown above. We can upgrade this now. Disable Module You must first disable the module. Here is a tutorial that covers how to disable a module Follow only part of the instructions listed there. Stop at the section labeled “Uninstalling a module” and only perform the first section of steps for disabling a module. Reinstall Module Next is reinstalling the module. The Update module also provides a download link which may be used to download the updated version instead of going to Drupal.org. Delete the outdated module's files and upload the new ones as if you were installing the module for the first time. Run update.php


Update.php is a script that is used to maintain websites after upgrades. A new version of a module may change the structure of the database. This script adjusts the database to fit the updated module. As the database contains all of the content and the settings of your website, this is an essential step to ensure its continued operation.

The update.php script can be called two different ways. You may go to the main administration page and click on the “update.php” link on the front page, or you may go directly to example.com/update.php in your browser.

You will require admin privileges to perform this upgrade. The first account created on your site will have the required privileges.

If you do not have the proper privileges, you will receive this screen. Either log in or follow the instructions on the page to perform the upgrade.

Once you have the proper privileges, you will see this screen. Click on the “Select versions” link to expand the section.

These dropdown boxes contain database updates specified by modules you have installed. If you are not developing your own module or troubleshooting, you should leave them alone and just click the “Update” button. The correct items should already be selected by default - either “No updates available” if there have not been any database changes, or a number, which is simply an ID number of the database upgrade being applied.

This next page may take a small amount of time to load, as the server is modifying your database. This page will display any errors that may have occurred during the process. If none have occurred, your module upgrade is complete! From TopNotchThemes.com

Directory precedence and multi-site


considerations Directory Precedence Contributed modules and themes can also be placed in the directories /sites/sitename/modules/ and /sites/sitename/themes/. Often, the sitename will be 'default'. Contributions placed there will only be available to the named site, whereas those under /all/* are available globally. For a single site setup, this probably won't make much difference to you, but if you ever start modifying downloaded code for your own use, it's a good idea to isolate your changes from the clean versions. It's possible to have two versions of any module (even core ones) available on the site. Your installation will choose from the most specific one available (first /sites/sitename/modules, then /sites/all/modules, then /modules). You can take advantage of this to test patches without damaging your existing files. Also, you can place modules anywhere within subfolders underneath any of these /modules/ folders. They will be searched recursively when you visit your admin/build/modules page. You can use this to further organise your available items. Multi-site Considerations The steps for installing in a multi-site configuration are much the same. The difference is where the modules and themes directory is located. If you use the /sites/all directory, then any module or theme installed under it will be available to all sites using the same code base. If you wish to limit the access to a module or theme to a specific site, then create a modules and/or themes directory under that sites folder. sites/example.com.site3/modules sites/example.com.site3/themes Anything in a site specific directory will not be accessible to other sites sharing the code base.

Migrating to Drupal This page contains hints and scripts from members of the Drupal community for migrating to Drupal from other content management systems (CMS) weblog, and bulletin board applications. Migrating from other platforms often requires knowledge of PHP and SQL. Migration involves mapping data fields from the original application's database into Drupal's database. For some applications this can a simple task, with tools or scripts available to do the migration. Other applications may have complex database schemas, lack documentation, and are uncommon enough that there are no tools available. The best way to find out if tools or scripts exist for your application is to search on Google. For example a search such as "migrate WordPress Drupal" returns dozens of useful links. If there are no tools available for migrating from your application to Drupal, you will want to familiarize yourself with Drupal's database schema, as well as the schema of the application you are migrating. You will need to map all your current users into Drupal's users table. If you have different roles (for example, read-only, author, editor/reviewer, admin), you will need to assign your users to properly set up and configured roles in Drupal. This can mostly be done through Drupal's admin interface, although if you have a large number of users, you may want to find a way to automate the assignment. Editing each user by hand could be time consuming. If the content being migrated is text, it would likely map into the node and node_revisions tables, with comments in the comments table. It is usually possible to import databases (MySQL, etc.) containing content and users from your previous CMS into Drupal. This can be done by exporting the databases first to CSV (comma-separated values) or similar files. Tools such as phpMyAdmin for MySQL can make this task easier. You can then import these files with import/export modules such as Node Import, User Import, CSV Parser, Migrate, or Transformations (see a module comparison). Note that content is often imported as Content Construction Kit (CCK) custom content types. If your tables are available in your Drupal database, you can use the Table Wizard to expose them to Views, and then use the Migrate module to copy the data from the old columns to the CCK content types. Also, the Node Convert module can convert imported node types to other node types; it supports, at least, CCK fields, book and forum nodes, and probably others like blog, etc. In the case that your chosen import module is not yet ready for the Drupal version you wish to use, then there is the workaround of installing a site running on a previous Drupal version just for the import, and then upgrading it. On the other hand, if you prefer to migrate data into Drupal developing a custom PHP script, see for example Migration tips for some techniques.


Support for migrating can be found on the Drupal.org forum, Converting to Drupal. See also the Drupal group Content migration, import, and export, for discussion of best practices in content migration. Drupal Groups also has a comparison of modules for importing and exporting data. Finally, look in the Drupal CVS contributions repository for a subdirectories named mt2drupal (code for migrating from Movable Type to Drupal), phpbb2drupal (SQL code for migrating from phpBB to Drupal) and slash2drupal (Slash 2.2 to Drupal) for SOME IDEAS. None of these is up to date with the latest releases of Drupal and the respective source systems, so they will NOT work with latest versions of any of the foregoing. However, they will get you close. The following pages describe methods people have used to migrate to Drupal in the past. As other CMS software and Drupal evolve, you can use these as a guide to help with your own.

Database import broken down to small pieces In order to import content to drupal's core or contrib modules, you need to understand how each component works, and how data is stored. The main items listed in http://drupal.org/node/307799 to import include; content, comments, files & images, users and roles, external services. In this section, we will delve into more detail as to how to import different items.

Inserting a simple node The following is the framework for code to create a very simple node, only populating the title field, not even the body. This label content type does not have any cck fields. // Create a mapping table if it does not exist db_query("CREATE TABLE IF NOT EXISTS {map_label} ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY , nid INT NOT NULL , label_id INT NOT NULL ) TYPE=MyISAM /*!40100 DEFAULT CHARACTER SET utf8 */;"); // Populate an array with drupal nids already imported $sql = "SELECT label_id FROM map_label WHERE nid != %d"; $result = db_query($sql, 0); while ($obj = db_fetch_object($result)) { $current[$obj->label_id] = $obj->label_id; } // Loop through all the old data in the postgres database $sql = "SELECT label_id, company_name, active FROM label"; $result = pg_query($sql); if (!$result) { print "Unable to retrieve data"; exit; } while ($obj = pg_fetch_object($result)) { // Only insert new data to drupal if (! array_key_exists($obj->label_id, $current)) { $n = new stdClass(); $n->uid = 36; $n->name = 'archive'; $n->type = 'ppl_label'; if ($obj->active == 'Y') { $n->status = 1; } $n->validated = 1; $n->title = trim($obj->company_name); node_save($n); // echo "$n->nid: $obj->label_id: $obj->company_name <br/>"; $ins_sql = "INSERT INTO map_label (label_id, nid) VALUES (%d, %d)"; db_query($ins_sql, $obj->label_id, $n->nid); } }

Inserting terms db_query("CREATE TABLE IF NOT EXISTS {map_terms} ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY , class_id INT NOT NULL , tid INT NOT NULL , vid INT NOT NULL ) TYPE=MyISAM /*!40100 DEFAULT CHARACTER SET utf8 */ ;"); function _taxonomy_get_tid($name, $vid) { $sql = "SELECT t.tid FROM {term_data} t WHERE t.name LIKE '%s' AND t.vid = %d"; $tid = db_result(db_query("SELECT t.tid FROM {term_data} t WHERE t.name LIKE '%s' AND t.vid = %d", trim($name), $vid)); return $tid; } // Connect to postgres $conn = pg_connect("dbname=db_123 user='db_user' password='db_pass'");


if (!$conn) { print "Unable to Connect to DB"; exit; } function insert_music_genre() { echo "========== Music Genre ==========<br/>"; $vid = 6; $sql0 = "SELECT class_id FROM map_terms WHERE tid != 0 AND vid = %d"; $result0 = db_query($sql, $vid); while ($obj = db_fetch_object($result0)) { $current[$obj->class_id] = $obj->class_id; } $sql = 'SELECT * FROM classification c '; $sql .= 'JOIN media_type_classification mc ON mc.class_id = c.class_id '; $sql .= "WHERE mc.media_type_id = 3 AND c.active = 'Y' ORDER BY class_name"; $result = pg_query($sql); if (!$result) { print "Unable to retrieve data"; exit; } $terms = array(); while ($obj = pg_fetch_object($result)) { if (! array_key_exists($obj->class_id, $current)) { $values = array('name' => $obj->class_name, 'vid' => $vid,); taxonomy_save_term($values); $tid = taxonomy_get_tid($obj->class_name, $vid); db_query("INSERT INTO map_terms (table_name, class_id, tid, vid) VALUES ('%s', %d, %d, %d)", 'classification', $obj->class_id, $tid, $vid); echo "$tid <br/>"; } } }

URL Redirection Steps Confirm with client the urls to be redirected Install path_redirect module Identify which urls can be generated from node/{nid} Identify which urls are views to be created Any remainder on the list not covered by node/{nid} or views, discuss with client possible solutions, and come to an agreement Analyze pattern of old url generation, find a logical pattern, usually can be obtained from tables in the old database create a script that uses the mapping of drupal's node table to old database (created earlier) to populate path_redirect table

Details With path redirect module, all their old links would work and redirect properly 301 to the new items. To test path redirect, they can go to the admin page /admin/build/path-redirect, or to really test, they could alter their hosts file to point to the beta server, and see what happens when they look up an old url. $rid = db_next_id('{path_redirect}_rid'); $old_url = 'music-reviews'; $new_url = 'reviews/music'; db_query("INSERT INTO {path_redirect} (rid, path, redirect, type) VALUES (%d, '%s', '%s', '%s')", $rid, trim($old_url, '/'), trim($new_url, '/'), '301');

Regeneration of url aliases If the customer changes their mind about the url structure, no need to regenerate most of the redirect links. The path redirect module works together with the url alias table to provide the correct url. delete the url aliases from url_aliases table using the menu option to delete aliases for all nodes, or with sql to delete certain aliases DELETE FROM url_alias WHERE src LIKE 'node%' AND dst LIKE 'movies/%';

note if the new pathauto pattern is similar or identical to the old url's populated in path_redirect, then please patch pathauto with http://drupal.org/node/261615 so that if the url auto-generated is identical, delete the row from path_redirect instead of creating the url in url_alias with -0 added.

Using geonames and importing location enabled nodes


function xmlfromplace($name, $country, $type) { $str = 'http://ws.geonames.org/search?q='; $str .= $name; if ($type == 'city') { $str .= '&featureClass=P&country='; } elseif ($type == 'province') { $str .= '&featureCode=ADM1&country='; } elseif ($type == 'country') { $str .= '&featureClass=A&country='; } else { $str .= '&country='; } $str .= $country; $str .= '&maxRows=1'; $xml = simplexml_load_file($str); return $xml; } function insertlocation($node, $city, $postal_code, $province, $country, $xml) { $lat = $xml->geoname->lat; $lon = $xml->geoname->lng; $l = array(); $l['city'] = $city; $l['province'] = $province; $l['country'] = $country; $l['lat'] = $lat; $l['lon'] = $lon; $l['source'] = 3; _location_save($l, $node, 'node'); }

Parameters for insertlocation node: full node object province: 2 letter abbreviation country: 2 letter abbreviation xml: result from xmlfromplace()

Location table For each location enabled node that is saved, a row is saved into the location table, where location.eid = node.vid mysql> describe location; +-------------+------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------+------------------+------+-----+---------+-------+ | eid | int(10) unsigned | NO | | 0 | | | lid | int(10) unsigned | NO | PRI | 0 | | | type | varchar(6) | NO | | | | | name | varchar(255) | YES | | NULL | | | street | varchar(255) | YES | | NULL | | | additional | varchar(255) | YES | | NULL | | | city | varchar(255) | YES | | NULL | | | province | varchar(16) | YES | | NULL | | | postal_code | varchar(16) | YES | | NULL | | | country | varchar(2) | YES | | NULL | | | latitude | decimal(10,6) | YES | | NULL | | | longitude | decimal(10,6) | YES | | NULL | | | source | tinyint(4) | YES | | 0 | | | is_primary | tinyint(4) | NO | | 0 | | +-------------+------------------+------+-----+---------+-------+ +-----+-----+------+------+--------+------------+-----------+----------+------------+---------+-----------+-------------+--------+------------+ | eid | lid | type | name | street | additional | city | province | postal_code | country | latitude | longitude | source | is_primary | +-----+-----+------+------+--------+------------+-----------+----------+------------+---------+-----------+-------------+--------+------------+ | 9 | 1 | node | | | | Vancouver | BC | | ca | 49.263588 | -123.138565 | 3 | 0 | +-----+-----+------+------+--------+------------+-----------+----------+------------+---------+-----------+-------------+--------+------------+

Get started by creating a list * Content * Comments * Files & Images * Users and Roles * External services, either relying or providing

Content Could be delivered in html/xml format or as a database dump. Need to determine how to organize content so that the drupal cck content types, taxonomies or other can be created.

Comments


Is there a commenting system and are the comments to be migrated.

Files & Images How are files stored, can the path be referenced directly, or do they need to be stored as nodes. For images, use the image module or imagefield?

Users and Roles What users if any to migrate over, how does the access system in the old site translate into drupal's user access system?

External Services Does the site provide feeds or other services? Or does the site depend on other external services such as Amazon, Youtube, etc. How will the interaction be translated?

Deciding when to use cck and taxonomy When importing to cck content types, a very good understanding is needed of the clients old database, what their business use cases are, how they interact with the data, and what are the new requirements. The content types have to be created and have approval from the client that, they work in the way the client is expecting, before the bulk of database migration is started.

Creating content types The customer may identify different grouping of pages that they are likely to create, e.g. news releases, products, companies. Depending on how each grouping of data interacts with each other, it could be either: content type content types can be as simple as all the data in the body field, or depending on how the client may want to sort and filter data, each piece of information segregated into fields make use of node reference fields to describe relationship between content types, e.g. a film has many actors, where both film and actor are both content types, since the client describes details about both films and actors if the client does not store, and will likely not store more details about a piece of data, e.g. festival, then festival would be a taxonomy instead. vocabulary/taxonomy/category used for a simple list of items. Very powerful tool. Not used when there are more details describing that vocabulary term. For example: book awards could be a vocabulary for one site, for another site, that is mainly concerned with awards, it would be a content type. As a content type, more details about each book award can be stored, sorted, filtered and searched. if in the first phase a type of content is stored as taxonomy, and then later the customer decides to expand the use of that content, no need to stress, it is not too difficult to convert taxonomy terms into nodes. custom field node/view reference field drop down cck fields be very wary and hesitant of using drop down cck fields mainly useful for static grouping of items, e.g. yes/no or monday/tuesday ... /sunday use taxonomy or node/view reference instead if listing is likely to be altered. If approaching content type bloat consider to combine content types together; for example instead of having ppl_actor, ppl_director content types, an alternative is to have people content type with a vocabulary of person type (actor, director, etc.). The benefit is that there would not be 2 or more nodes describing a single person. With 2 or more nodes of the same entity, it becomes difficult to accurately show all movies Paris Hilton acted in as well as all songs Paris Hilton sung in with a simple view. The proper data architecture, as in proper for that client, not proper for all clients, will determine how easy or difficult it is to accommodate likely future feature requests. This will impact the maintainability of the website as well.

Use mapping tables to control data import


Create a mapping table to record the drupal nid to the unique id found on the old database. The purpose is to have a way of tracking the work done, and to have a way of double checking. Also, it is used to know what data has already been inserted, and what new data has not yet been inserted. * the minimum columns in a mapping table would be ** id : just the usual unique id for a table ** nid: node nid created in drupal ** label_id: unique id on the old database ** other columns can be created as needed, for quick reference and checking.

How to migrate from many different forum systems When there is not yet a Drupal module for direct conversion from your current forum software, migration can often be done in two steps, via popular forums with many converters available. Possible procedures are for example: Old forum -> phpBB -> Drupal Old forum -> vBulletin -> Drupal

Migration to Drupal via phpBB For the Old forum -> phpBB step, see phpBB 3.0.x Convertors and phpBB 2.0.x Convertors. If you need to install the old phpBB2 version, see Download legacy release (phpBB, like Drupal, is open source). For the phpBB -> Drupal step, see Drupal's phpBB2Drupal module, and the handbook section Migrating from phpBB.

Migration to Drupal via vBulletin For the Old forum -> vBulletin step, there are modules in vBulletin's ImpEx Import System to import from a large number of forum systems. For the vBulletin -> Drupal step, see the vBulletin to Drupal module, and the handbook page Migrating from vBulletin.

Features and performance For a Drupal forum with features similar to those of well-known forum systems such as phpBB and vBulletin, see the Advanced Forum module. For good speed and performance similar to those of server-friendly static html files used by some forum systems, see Drupal's Boost static cache for anonymous users, and other performance modules.

See also The Migrating to Drupal handbook section includes general methods, and also sub-sections on migrating from specific software systems, such as forums and CMS.

How to preserve old URLs during Drupal to Drupal migration by preserving nids and using mod_rewrite The problem: When migrating content into a Drupal site, no contributed modules provide a way to preserve old NID's during content import when consolidating several Drupal sites into a single installation. Please post counterexamples to this thread, if you know any. Therefore, it is impossible to preserve old URLs with those modules. Without a way of mapping the old NIDs to their new NIDs, it is impossible to ensure that inbound links from other sites will be preserved. My particular challenge: This is a write up of how to merge three sites into one while preserving NIDs. When a request comes from oldsite2.com/node/23 it forwards to that nodes new location on the new site, newsite.com/node/2023, likewise from oldsite1.com/node/23 to newsite.com/node/4023. See the problem this solves? I imagine that most people won't find this entire post particularly useful, but you can take parts of it as you might need. This was a requirement for our client, and so I underwent a somewhat lengthy process to do this. There are probably several steps of this process that can be handled in a different way (for instance, the awk script could have been an SQL or PHP script). Feel free to post those to this thread. I post this as a conceptual overview and some of my code so that others might benefit. I owe


much to the Aquia migration blog post on this subject. The solution overview: Migrate your old NIDs in a programmatic fashion. For instance, if an old site's node had a NID of 215, make it 2215, or , if it had a NID of 3, make it 2003. Add 2000 to the NIDs in the old database, then export them, and import them into the new database (using PHPmyAdmin or Drush or something). We also added 2000 to all comments and terms and things and imported them. We also mapped old input formats to new ones. The scripts below are just SAMPLES, and cannot be run verbatim. Also export the url_alias table, and iterate through it with the language of your choice, adding 2000 to the NIDs in that file. I used awk. Next, any URL that comes from the old site's address, check it for paths /node/nid, and rewrite it with mod_rewrite in the .htaccess file. At the end of this process, any URL coming from oldsite.org/node/1 will be redirected and rewritten to newsite.org/node/2001. You can migrate in two sites, modifying these scripts to add 6000 to the url of the second site, so that oldsite2.org/node/1 will be redirected to oldsite.org/node/6001. As you may have guessed, this tutorial is not for the faint of heart. First, upgrade all your sites to the current version of Drupal, in my case Drupal 6. If you're doing this in Drupal 7, you'll have to modify this tutorial accordingly. You want to migrate from one table to another preserving structure. I ran into this issue when upgrading from 4.7 with Spanish characters and solved it like this NEXT: CREATE A COPY OF YOUR OLD DATABASE. RUN THESE SCRIPTS ON A COPY, NOT ON YOUR ORIGINAL. 2. Run your SQL scripts. Here are mine: # add 2000 to nids, vids, and uids in node and node_revisions UPDATE node SET nid = nid+2000; UPDATE node SET vid = vid+2000; UPDATE node SET uid = uid+2000; UPDATE node_revisions SET nid = nid+2000; UPDATE node_revisions SET vid = vid+2000; UPDATE node_revisions SET uid = uid+2000; # map input formats/filters to new UPDATE node_revisions SET format = UPDATE node_revisions SET format = UPDATE node_revisions SET format = UPDATE node_revisions SET format = UPDATE node_revisions SET format =

formats/filters replace( format, replace( format, replace( format, replace( format, replace( format,

2, 3, 5, 4, 6,

6 2 3 5 3

) ) ) ) )

; ; ; ; ;

## correct a mistake I made!!! (in case you make a mistake) UPDATE node_revisions SET format = replace( format, 1, 1 ) WHERE nid > 1999; ##these are so that attachments work. UPDATE files SET fid = fid+2000; UPDATE upload SET fid = fid+2000; UPDATE upload SET nid = nid+2000; UPDATE upload SET vid = vid+2000; ##make the user ids that created the files match. an access denied error UPDATE files SET uid = uid+2000;

Otherwise you'll probably get

## or, on another note, the following code can be used alternately to change UID's from one to another. This has been useful to me in other projects. UPDATE files SET uid = replace( uid, 8, 2 ) ; UPDATE files SET uid = replace( uid, 4, 55 ) ; ##this is to update the new location of the files, if it's changed. Maybe you'll need it, maybe not. UPDATE files SET filepath = replace( filepath, 'sites/default/files', 'sites/newsite/files' ) ;

This next part was written by my co-worker, Josue. ## term_data UPDATE term_data UPDATE term_data UPDATE term_data UPDATE term_data

SET SET SET SET

tid vid vid vid

= = = =

tid+2000; replace( vid, 3, 15 ); replace( vid, 4, 16 ); replace( vid, 5, 2 );

##vocabulary UPDATE vocabulary SET vid = replace( vid, 3, 15 ); UPDATE vocabulary SET vid = replace( vid, 4, 16 ); UPDATE vocabulary SET vid = replace( vid, 5, 2 ); ##vocabulary_node_types UPDATE vocabulary_node_types SET vid = replace( vid, 3, 15 ); UPDATE vocabulary_node_types SET vid = replace( vid, 4, 16 ); UPDATE vocabulary_node_types SET vid = replace( vid, 5, 2 ); ## term_hierarchy UPDATE term_hierarchy SET tid = tid+2000; ## term_node


UPDATE term_node SET nid = nid+2000; UPDATE term_node SET tid = tid+2000; UPDATE term_node SET vid = vid+2000; #users UPDATE users SET uid = uid + 2000; #users_roles UPDATE users_roles SET uid = uid + 2000; #comments UPDATE comments SET cid = cid + 2000; UPDATE comments SET nid = nid + 2000; UPDATE comments SET uid = uid + 2000;

After running your (MODIFIED) scripts, export each of the modified tables, and then import them using phpmyadmin or mysql commands or drush or whatever. Now of the url_alias table. This was tricky because the path looks something like /node/23. You can add 2000 to a string. You have to pull out the number, add 2000, then rebuild it. Nice, huh? So, export your old url_alias table, as is. With a good text editor, chop the first part off the file so that it's just the data, and not the table definitions. Then, modify this awk shell script and run it. I used gawk in linux, that is, gnu awk. Manual here. Yes, PHP could be used, too, to modify the tables in the database. Awk, however, is designed to regard text files as databases and provide complex string manipulation. While this script worked for me, it should be tested and tweaked. This script must be executed on a csv export of a database (I exported from phpmyadmin) fields terminated by |. The quotation marks enclosing a field must be searched for and deleted. The data should look like this: 4407|taxonomy/term/855/0/feed|tag/vegetarianism/feed| 4408|taxonomy/term/856|tag/soul-force| 4409|taxonomy/term/856/0/feed|tag/soul-force/feed| You can modify it afterwards to make it into an SQL file with inserts by combining it with an SQL export done the normal way. Execute it like #script.sh url_aliases_export.sql > your_output_file.sql #!/bin/bash # this requires a csv file with a fields terminated by | and the fields enclosed with ' Export using phpmyadimin TEMPFILE=/tmp/pd.key.tempfile.$$ TEMPFILE2=/tmp/pd.key.tempfile2.$$ NID=/tmp/pd.key.nid.$$ NID2=/tmp/pd.key.nid2.$$ trap "exit 1" HUP INT PIPE QUIT TERM trap "rm -f $NID" EXIT #sed -e 's/\|\|/\|\'\'\|/g' sed -e '/^$/d' < $1 > $TEMPFILE a=(blank blank2) awk -v Q="'" -v NID2=0 'BEGIN { FS = "|"; OFS = "," } { gsub(Q, "", $1) ; gsub(Q, "", $2) ; $1 = $1 + 200000; match($2, /[0-9][0-9]?[0-9]?[0-9]?[0-9]?/, a) ; NID2 = a[0] + 2000 ; gsub(a[0], NID2, $2) } { print "(" Q $1 Q , Q $2 Q ,Q $3 Q , Q "en" Q "),"}' $TEMPFILE

Check over this file. You may need to add a semicolon on the end, and clean it up a bit. Make sure it's right. Compare it to your other file. Paste the old table definitions stuff back in. Import it into your new database. It may catch on some URLS that have already been defined. Just delete the first half of the file (the part already imported) minus the field definitions, and delete the offending line in your import sql. See if your nodes and url aliases work as they are. You'll have to rebuild site permissions now or you'll get "access denied" errors over at Content -> Post Settings: admin/content/node-settings. Now for the mod_rewrite magic. For more info, check out the migration blog and the mod_rewrite online documentation, a nice introduction, tons of examples, or this cheat sheet. Finally, the most important thing to remember is that the RewriteCond is checking internal server variables. the RewriteRule is just checking and rewriting the URL. This doesn't change the internal server variables. Paste the following into your .htaccess file. # check each url and see if it is from the old sites. to the node. If oldsite2.net, add 2000.

If oldsite1.org, add 6000

RewriteCond %{HTTP_HOST} ^oldsite1\.org$ [NC] RewriteCond %{QUERY_STRING} ^q=node/([0-9]{3})$ [NC] RewriteRule ^.*$ http://newsite.org/index.php?q=node/6%1 [R=301,L] RewriteCond %{HTTP_HOST} ^oldsite1\.org$ [NC] RewriteCond %{QUERY_STRING} ^q=node/([0-9]{2})$ [NC] RewriteRule ^.*$ http://newsite.org/index.php?q=node/60%1 [R=301,L]


RewriteCond %{HTTP_HOST} ^oldsite1\.org$ [NC] RewriteCond %{QUERY_STRING} ^q=node/([0-9]{1})$ [NC] RewriteRule ^.*$ http://newsite.org/index.php?q=node/600%1 [R=301,L] RewriteCond %{HTTP_HOST} ^oldsite2\.net$ [NC] RewriteCond %{QUERY_STRING} ^q=node/([0-9]{3})$ [NC] RewriteRule ^.*$ http://newsite.org/index.php?q=node/2%1 [R=301,L] RewriteCond %{HTTP_HOST} ^oldsite2\.net$ [NC] RewriteCond %{QUERY_STRING} ^q=node/([0-9]{2})$ [NC] RewriteRule ^.*$ http://newsite.org/index.php?q=node/20%1 [R=301,L] RewriteCond %{HTTP_HOST} ^oldsite2\.net$ [NC] RewriteCond %{QUERY_STRING} ^q=node/([0-9]{1})$ [NC] RewriteRule ^.*$ http://newsite.org/index.php?q=node/200%1 [R=301,L] RewriteRule !^http://newsite.org$ '-' [C] RewriteRule ^http://[.*]/([.*])$ '-' [C] RewriteRule ^(.*)$ http://newsite.org/$1 [R=301,L] # rewrite all www's to the base name. Necessary to not write exceptions to the above rules and make things nice and clean RewriteCond %{HTTP_HOST} ^www\.oldsite1\.org$ [NC] RewriteRule ^(.*)$ http://oldsite1.org/$1 [L,R=301] RewriteCond %{HTTP_HOST} ^www\.oldsite2\.net$ [NC] RewriteRule ^(.*)$ http://oldsite2.net/$1 [L,R=301] RewriteCond %{HTTP_HOST} ^www\.newsite\.org$ [NC] RewriteRule ^(.*)$ http://newsite.org/$1 [L,R=301] #rewrites anything from the old sites that that don't have node in them (all old urls) to newsite.org/whatever_the_old_one_was RewriteCond %{HTTP_HOST} ^(oldsite2\.net)$ [NC] RewriteRule !^.*node.*$ '-' [C] RewriteRule ^(.*)$ http://newsite.org/$1 [R=301,L] RewriteCond %{HTTP_HOST} ^(oldsite1\.org)$ [NC] RewriteRule !^.*node.*$ '-' [C] RewriteRule ^(.*)$ http://newsite.org/$1 [R=301,L]

[C] means execute the following if and only if the current rule succeeds. [NC] means capitalization is irrelevant. [R=301] means permanent redirect. [L] Means last rule, quit processing () captures the enclosed and creates a variable that can be accessed later using $1 or %1 To troubleshoot this (surely it will all work perfectly!), enable devel module's execute php block and execute print_r($GLOBALS); This will spit out all the internal apache server variables, which you can use to check to see if the RewriteCond can see the right stuff. Make sure you do this temporarily, don't print your variables anywhere in your theme or anything. That would be really bad, as the code contains your main database password and username. Other than that, it's all probably working without a hitch now, and I suggest a cold beer. You deserve it.

Migrating from ACT! When I imported 3000 or so nodes from my ACT! database into a local Drupal site it all worked well but because all the dates were in dd/mm/yy format they would not import using Node import . I opened the .csv file in Excel and added two columns: '(unix) Create Date' for Create Date and '(unix) Edit Date' for Edit Date I then used this formula to make the Unix timestamp =IF(A1, (A1-25569-(-5/24))*86400,"") Note: A1 is the cell that contains the dd/mm/yy or mm/dd/yy (your computer knows about the way you format dates in your country) and the 5 is to set the time to 5am.

Migrating from Bricolage If a customer has a Bricolage site, and wants to use some of the features of drupal, they can try out the Bricolage Integration module: http://drupal.org/project/bricolage Otherwise, it is also possible to migrate from a Bricolage project entirely to drupal. To migrate data, I analyzed the database, to get unique ids (story__id) and the directory structure of the generated html files. Taxonomy and/or content type was determined from the directory structure, and main content was parsed from the html files. * determine the bricolage story__id associated with types of content to be migrated from Bricolage, e.g. 1041 for article, 1054 for blog, 1063 for review, 1065 for artist. * Identify all the story__id's that need to be imported, and store them in a mapping table to track or trace what stories have already been imported.


SELECT id, primary_uri, publish_date FROM story WHERE element__id IN (1041, 1054, 1063, 1065) AND active != 0

* Some data was obtained from the database by joining the story table (~ drupal's node table) to story_instance table (~ drupal's node_revisions), for example short_val equated to the stories subtitle when story_data_tile.element_data__id = 1070. In bricolage each paragraph or other item has its own row in the story_data_tile table. SELECT s.id, s.primary_uri, s.publish_date, d.short_val FROM story s JOIN story_instance i ON s.id = i.story__id AND s.current_version = i.version JOIN story_container_tile t ON t.object_instance_id = i.id JOIN story_data_tile d ON t.id = d.parent_id WHERE t.element__id = 1041 AND d.element_data__id = 1070 AND d.active != 0 AND s.element__id IN (1041, 1054, 1063, 1065) AND s.active != 0

* other data was obtained by parsing the html files using fopen() and preg_match() * image paths were replaced by using preg_replace() and the old and new path stored in a mapping table for images, which were converted into imagefields for the story. $img['path'] = trim(preg_replace("/(.*?)[\\\"|'](.*?)[\\\"|'](.*)/", "$2", $buffer));

Migrating from CPG Dragonfly CMS Note: heavy editing in progress Migrating from CMS Dragonfly CMS consists of two parts: first, migrating the forums using the phpBB2Drupal module (which migrates the users, profiles, forums, forum posts and replies, private messages, and polls) and second, migrating everything else (the articles, categories, roles...).

Migrating the forums 1. Install Drupal and the following modules: phpBB2Drupal BBCode Comment Upload PrivateMsg 2. Enable all of the above modules, plus: Forum Poll Profile 3. Create a BBCode input format, and set as the default: 1. Go to administer >> input formats (4.7.x) or Administer >> Site Configuration >> Input Formats (5.x). 2. Click Add input format, and enter the following options: Name: BBCode Roles: anonymous user, authenticated user Filters: BBCode 3. Click Save. 4. Back at the main input formats screen, click the radio next to BBCode and press Set default format. 4. Apply the DragonflyCMS patch to phpBB2Drupal module. 5. Configure the php2Drupal migration: 1. Click administer >> phpBB to Drupal (4.7.x) or Administer >> X >> phpBB to Drupal (5.x) 2. Click Configure the migration 3. Enter the following settings: Test on copy first > checked Input format settings > Input format: BBCode Location of phpBB2 data > phpBB2 table prefix: cms_bb Misc settings > Convert Registration Date: checked Polls import: Import polls? checked Private Messages > Import private messages? checked 4. Click Save configuration 6. Make sure you have a backup of your database, and then click Execute the migration from the main phpBB2Drupal screen. 7. Execute each step of the migration by clicking the Import button. Each step will import separate data from phpBB.

Migrating everything else Below is the beginning of a script to handle migration from CPG Dragonfly CMS 9.0.6.1 to Drupal 4.7. I'll post updates as I get more completed in the coming weeks. IGNORE the rest of this page for now!! <?php // $Id$ /** * @file


* Handles data import from CPG Dragonfly CMS 9.0.6.1 to Drupal 4.7. Or will someday, * when I get it finished. :P For now it only handles user and profile fields. * * To use: * 1. Save this script as "dragonfly2drupal.php" in the root of your Drupal installation. * 2. Change the DRAGONFLY_PREFIX variable if needed -- defaults to 'cms_' * 3. In your settings.php file, change the $db_url as follows: * * BEFORE: * $db_url = 'mysql://user:pass@host/drupal_db'; * * AFTER: * $db_url['default'] = 'mysql://user:pass@host/drupal_db'; * $db_url['dragonfly'] = 'mysql://user:pass@host/dragonfly_db'; * */ include_once "includes/bootstrap.inc"; drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); /** Dragonfly CMS table prefix **/ define('DRAGONFLY_PREFIX', 'cms_'); echo '<h2>Importing users...</h2>'; dragonfly_user_import(); echo '<h2>Done.</h2>'; echo '<h2>Adding profile fields...</h2>'; dragonfly_add_profile_fields(); echo '<h2>Done.</h2>'; echo '<h2>Importing profile fields...</h2>'; dragonfly_user_profile_import(); echo '<h2>Done.</h2>'; echo '<h2>Importing topics...</h2>'; dragonfly_import_topics(); echo '<h2>Done.</h2>'; echo '<h2>Importing blogs...</h2>'; dragonfly_import_blogs(); echo '<h2>Done.</h2>'; /* FUNCTION DECLARATIONS */ function dragonfly_user_import() { // Drupal => Dragonfly field mappings $user_fields = array( 'uid' => 'user_id', 'name' => 'username', 'pass' => 'user_password', 'mail' => 'user_email', 'created' => 'user_regdate', 'access' => 'user_lastvisit', 'login' => 'user_session_time', 'status' => 'user_level', 'init' => 'user_email', ); // Retrieve all user records from Dragonfly except anonymous user (#1) db_set_active('dragonfly'); $dragonfly_users = db_query('SELECT * FROM %susers WHERE user_id != 1 ORDER BY user_id', DRAGONFLY_PREFIX); // Back to Drupal; handle any data conversion and then import the user info db_set_active('default'); while ($user = db_fetch_object($dragonfly_users)) { // Convert registered time $user->user_regdate = strtotime($user->user_regdate); // Set all users but blocked users to active if ($user->user_level != 0) { $user->user_level = 1; } // // // // //

TODO: user avatar handling Dragonfly lets you choose from a pre-existing avatar in the gallery, upload a picture to the site, or link to an external image -- these all need to go under files/pictures/picture-XX.png|jpg|gif, where XX is the user's uid.

// TODO: signature handling // The tricky part here is that Dragonfly's signatures are in BBCode, // which Drupal can't parse natively. // TODO: Handle blocked users // Generate SQL string $sql = drupal_generate_sql_string('users', $user, $user_fields); db_query($sql); } // Increase sequence in sequences table $uid = db_result(db_query("SELECT MAX(uid) FROM {users}")); $sql = "UPDATE {sequences} SET id = $uid WHERE name = 'users_uid'"; db_query($sql);


} function dragonfly_add_profile_fields() { // Real name $profile_values = array( 'title' => t('Real Name'), 'name' => 'profile_real_name', 'category' => t('Profile Information'), 'type' => 'textfield', 'visibility' => 1, 'weight' => -4, ); profile_field_form_submit(NULL, $profile_values); // Home Page $profile_values = array( 'title' => t('Website'), 'name' => 'profile_website', 'category' => t('Profile Information'), 'type' => 'textfield', 'visibility' => 2, 'weight' => -2, ); profile_field_form_submit(NULL, $profile_values); // TODO: My Location - use Location/Gmap module for this? // My Occupation $profile_values = array( 'title' => t('My Occupation'), 'name' => 'profile_occupation', 'category' => t('Profile Information'), 'type' => 'textfield', 'visibility' => 2, 'weight' => 0, ); profile_field_form_submit(NULL, $profile_values); // My Interests $profile_values = array( 'title' => t('My Interests'), 'name' => 'profile_interests', 'category' => t('Profile Information'), 'type' => 'textfield', 'visibility' => 2, 'weight' => 2, ); profile_field_form_submit(NULL, $profile_values); // Bio $profile_values = array( 'title' => t('Bio'), 'name' => 'profile_bio', 'category' => t('Profile Information'), 'type' => 'textarea', 'visibility' => 2, 'weight' => 4, ); profile_field_form_submit(NULL, $profile_values); } function dragonfly_user_profile_import() { $profile_fields = array( 'name' => 'profile_real_name', 'user_website' => 'profile_website', 'user_occ' => 'profile_occupation', 'user_interests' => 'profile_interests', 'bio' => 'profile_bio', ); // Get field IDs for the various profile fields $fids = array(); foreach ($profile_fields as $key => $value) { $fids[$key] = db_result(db_query("SELECT fid FROM {profile_fields} WHERE name = '$value'")); } // Retrieve profile fields from Dragonfly $fields = implode(', ', array_keys($profile_fields)); db_set_active('dragonfly'); $result = db_query("SELECT user_id, $fields FROM %susers ORDER BY user_id", DRAGONFLY_PREFIX); // Import data db_set_active('default'); while ($user = db_fetch_array($result)) { $uid = $user['user_id']; foreach ($user as $key => $value) { if (!empty($value) && $key != 'user_id') { db_query("INSERT INTO {profile_values} (fid, uid, value) VALUES (%d, %d, '%s')", $fids[$key], $uid, $value); } } } } // Taxonomy function dragonfly_import_topics() { $vocabulary = array( 'name' => t('Topic'),


'nodes' => array('story'), ); //taxonomy_save_vocabulary($topic); // Get vocabulary ID //$vocabulary['vid'] = db_result(db_query("SELECT id FROM {sequences} WHERE name = '%s'", db_prefix_tables('{vocabulary}_vid'))); // Grab all topics db_set_active('dragonfly'); $result = db_query("SELECT * FROM %stopics ORDER BY topicid", DRAGONFLY_PREFIX); $terms = array(); while ($topic = db_fetch_array($result)) { $terms[] = array( 'tid' => $topic['topicid'], //'vid' => $vocabulary['vid'], 'name' => $topic['topictext'], ); } db_set_active(); // Write XML file // Note: This line messes up codefilter -- change to ? > without a space. $xml = '<?xml version="1.0" standalone="no"? >' . "\n"; $xml .= '<!DOCTYPE taxonomy SYSTEM "taxonomy.dtd">' . "\n"; $xml .= "<vocabulary>\n"; foreach ($vocabulary as $key => $value) { if (is_array($value)) { $xml .= " <$key>" . check_plain(implode(',', $value)) . "</$key>\n"; } else { $xml .= " <$key>" . check_plain($value) . "</$key>\n"; } } foreach ($terms as $term) { $xml .= " <term>\n"; foreach ($term as $key => $value) { $xml .= " <$key>" . check_plain($value) . "</$key>\n"; } $xml .= " </term>\n"; } $xml .= "</vocabulary>\n"; taxonomy_xml_parse($xml); // TODO: images } function dragonfly_import_blogs() { db_set_active('dragonfly'); $result = db_query("SELECT * FROM %sblogs", DRAGONFLY_PREFIX); db_set_active(); while ($post = db_fetch_object($result)) { $blog = new stdClass(); $blog->type = 'blog'; $blog->title = $post->title; $blog->body = $post->text; $blog->teaser = node_teaser($post->text); $blog->uid = drupal_get_user_id($post->aid); $blog->status = 1; $blog->created = $post->timestamp; $blog->changed = $post->timestamp; $blog->comment = 2; node_save($blog); } } function drupal_get_user_id($username) { return db_result(db_query("SELECT uid FROM {users} WHERE name = '%s'", $username)); } function drupal_generate_sql_string($table, $object, $mapping) { $fields = array(); $values = array(); foreach ($mapping as $key => $value) { $fields[] = $key; $values[] = $object->$value; } $fields = implode(', ', $fields); $values = implode("', '", $values); return "INSERT INTO {$table} ($fields) VALUES ('$values')"; } ?>

Migrating from DCForum+ This work is not cleaned up, but then again I've been meaning to clean it up since November! Trying to start 2006 on a fresh slate so here goes. However, basic thing is that it works. It is not recommended for those who are not comfortable with php/mysql. And then again, if you're doing/thinking about doing this in the first place then you've either got a lot of chutzpah or you know what you are doing. Functionality:


There are two scripts to be run in order which together allows you to migrate dcforum+ (the mysql database backed version) user accounts (usernames and profiles + password recognition), and forum posts. Live example: http://research.yale.edu/swahili/learn just migrated to Drupal from dcforum+ and a homecooked CMS. 1. dcforumintegration.module (Depends on forum.module, and flatforum.module) Enable it and run as the first admin user so that it won't be available to any other user. Enabling the module will generate a menu link called "migrate dcf". On clicking, there will be links to a. Migrate usernames and emails [Click the "Undo" link to reverse this process] b. Migrate forum containers [Click the "Undo" link to reverse this process] c. Migrate forum posts + responses (as Drupal comments) [Click the "Undo" link to reverse this process] The "Undo" links were put there for my own sanity. 2. dcprofilesmigration.module a. Create the profile fields you want to migrate using the profile.module b. Enabling this module generates a menu item called "mdata". On clicking, there will be a page that lists all the profile columns in dcforum (from the dcuser table). Each column has a drop-down list with all the Drupal profile fields. c. Match the Drupal profile fields to the dcuser profile field of your choice and select the checkboxes of the matched fields. d. Click the Submit button at the bottom of the page to run the profile migration SQL queries. The generated queries will also be displayed in the textarea at the bottom of the page for manual inspection. Hope this helps someone.

dcforumintegration.module <?php /* * dcforumintegration.module * From: DCForum+ User Accounts and Forum * To Drupal forum and flatforum module. * Passwords are migrated by modifying user.module * Remember to search and replace "$my_dcf_dbase" with the name of the dcforum+ mysql database. */ function dcforumintegration_menu($may_cache) { global $user; $items = array(); $admin_access = user_access('administer site configuration'); $items[] = array('path' => 'dcf', 'title' => t('migrate dcf'), 'callback' => 'actions', 'access' => $admin_access); $items[] = array('path' => 'dcf/users', 'title' => t('migrate dcf users'), 'callback' => 'actions', 'access' => $admin_access); $items[] = array('path' => 'dcf/forums', 'title' => t('migrate dcf forums'), 'callback' => 'actions', 'access' => $admin_access, weight=> '1'); return $items; } //------------HELPER FUNCTIONS------------------//

function actions() { switch(arg(1)) { case 'users': $output = migrate_users(); break; case 'forums': { if(arg(2) == "toplevel") { $output = migrate_containers(); $output.= migrate_forums(); } else if(arg(2) == "undotoplevel") { $output = unmigrate_containers(); $output.= unmigrate_forums(); } else if(arg(2) == "topposts") $output = migrate_topposts(); else if(arg(2) == "undotopposts") $output = unmigrate_topposts(); } break;


default: $output = "<li>".l('Migrate Users', 'dcf/users')."</li><br/><b>Note that all users except the root user will be deleted from the database</b>"; $output .= "<li>".l('Migrate Forums', 'dcf/forums')."</li><br/><b>Perform the following in order <br/>Remember to augment the node, comment tables with the two fields from $my_dcf_dbase</b>"; $output .= "<ul><li>".l('Migrate Top Level Forums', 'dcf/forums/toplevel')." [".l('Undo', 'dcf/forums/undotoplevel')."]</li>"; $output .= "<ul><li>".l('Migrate Top Level Posts + comments/responses)', 'dcf/forums/topposts')." [".l('Undo', 'dcf/forums/undotopposts')."]</li>"; break; } print theme('page',$output); } function migrate_users() { # Step 1: Migrate Users db_query("DELETE FROM {users} WHERE uid!=1"); db_query("INSERT INTO {users} (uid) VALUES(0);"); db_query('INSERT INTO {users} (uid, name, pass, mail, signature, mode, status, init, data, created, changed) SELECT id, username, password, email, pk, 0, 1, email, "N;", reg_date, last_date FROM $my_dcf_dbase.dcuser WHERE id!=1;'); db_query("DELETE FROM {users_roles};"); db_query("INSERT INTO {users_roles}(uid, rid) VALUES(0, 1);"); db_query("INSERT INTO {users_roles}(uid, rid) VALUES(1, 2);"); db_query("INSERT INTO {users_roles} (uid, rid) SELECT uid, 2 FROM {users} WHERE uid != 1 AND uid !=0;"); # Set the user sequence! db_next_id("user"); watchdog('dcfmigration', "All users deleted, populated with dcuser entries", 1); return "All users deleted, populated with dcuser entries".l('View Imported Users', 'admin/user'); } function migrate_containers() { # number of vocabulary groups I already have $numvocgroups = db_fetch_object(db_query("SELECT id FROM {sequences} WHERE name = 'vocabulary_vid'")); $numvocgroups = $numvocgroups->id; # Step 3: Migrate toplevel containers # Remember to clear forum* variables from the variables table db_query("DELETE FROM {variable} WHERE name LIKE 'forum%'"); db_query("INSERT INTO {term_data} (tid, vid, name, description, weight) SELECT id, %d, name, description, forum_order FROM $my_dcf_dbase.dcforum WHERE parent_id='0';",$numvocgroups ); $res = db_query("SELECT id FROM $my_dcf_dbase.dcforum WHERE parent_id='0'"); #Make them containers $containers = variable_get('forum_containers', array()); while($tid = db_fetch_object($res)) { $containers[] = $tid->id; } variable_set('forum_containers', $containers); db_query("INSERT INTO {term_hierarchy} (tid, parent) SELECT tid, 0 FROM {term_data;}"); db_query("REPLACE INTO {sequences} (id, name) SELECT max(tid), 'term_data_tid' FROM {term_data}"); # Set the term_data_id sequence! return "Top level containers ".l('View forums', 'forum'); } function unmigrate_containers() { $tids = db_query("SELECT id, name FROM $my_dcf_dbase.dcforum WHERE parent_id='0'"); while($tid = db_fetch_array($tids)) { db_query("DELETE FROM {term_data} WHERE tid = %d ", $tid); db_query("DELETE FROM {term_hierarchy} WHERE tid = %d ", $tid); $output.= "<li>".$tid['name']; } variable_set('forum_containers', array()); db_query("REPLACE INTO {sequences} (id, name) SELECT max(tid), 'term_data_tid' FROM {term_data}"); # Set the term_data_id sequence! return $output."<br><b>Top Level Containers Deleted</b>"; } function migrate_forums() { # number of vocabulary groups I already have $numvocgroups = db_fetch_object(db_query("SELECT id FROM {sequences} WHERE name = 'vocabulary_vid'"));


$numvocgroups = $numvocgroups->id; # Step 3: Migrate forums (these have parent_id's > 0) db_query("INSERT INTO {term_data} (tid, vid, name, description, weight) SELECT id, %d, name, description, forum_order FROM $my_dcf_dbase.dcforum WHERE parent_id >0 ;",$numvocgroups ); db_queryd("INSERT INTO {term_hierarchy} (tid, parent) SELECT id, parent_id FROM $my_dcf_dbase.dcforum WHERE parent_id > 0"); db_query("REPLACE INTO {sequences} (id, name) SELECT max(tid), 'term_data_tid' FROM {term_data}"); # Set the term_data_id sequence! return "Migrated Forums "; } function unmigrate_forums() { $tids = db_query("SELECT id, name FROM $my_dcf_dbase.dcforum WHERE parent_id > 0"); while($tid = db_fetch_array($tids)) { db_query("DELETE FROM {term_data} WHERE tid = %d ", $tid); db_query("DELETE FROM {term_hierarchy} WHERE tid = %d ", $tid); $output.= "<li>".$tid['name']; } db_query("REPLACE INTO {sequences} (id, name) SELECT max(tid), 'term_data_tid' FROM {term_data}"); # Set the term_data_id sequence! return $output."<br><b>Forums Deleted</b>"; } function migrate_topposts() { //Need to update 5 main tables: forum, node, node_comment_statistics, term_node, flat_forum db_query("INSERT INTO {flatforum} (posts, uid) SELECT num_topics, uid FROM $my_dcf_dbase.dcforum, {users} WHERE {users}.name = last_author;"); # number of toplevel posts I have $initnodeoffset = db_fetch_object(db_query("SELECT id FROM {sequences} WHERE name = 'node_nid';")); $initnodeoffset = $initnodeoffset->id; # add all the forum topics (posts become comments to these topics) $allforums = db_query("SELECT id FROM $my_dcf_dbase.dcforum WHERE parent_id > 0"); //$allforums = db_query("SELECT id FROM $my_dcf_dbase.dcforum WHERE id = 5"); while($eachforum = db_fetch_object($allforums)) { db_query('INSERT INTO {node} (forum_id, topic_id, type, title, uid, status, created, comment, promote, moderate, teaser, body, changed, revisions) SELECT %d, t.id, "forum", t.subject, t.author_id, 1, unix_timestamp(t.mesg_date), 2, 0, 0, t.subject, t.message , unix_timestamp(t.last_date) , "" FROM $my_dcf_dbase.%d_mesg t WHERE parent_id = 0', $eachforum->id, $eachforum->id); #Update the forum table db_query("INSERT INTO {forum} (nid, tid) SELECT nid, %d FROM $my_dcf_dbase.%d_mesg t, {node} n WHERE n.topic_id = t.id", $eachforum->id, $eachforum->id, $initnodeoffset, $eachforum>id);

#Update the term_node table db_query("INSERT INTO {term_node} (nid, tid) SELECT nid, %d FROM $my_dcf_dbase.%d_mesg t, {node} n WHERE n.topic_id = t.id", $eachforum->id, $eachforum->id, $initnodeoffset, $eachforum>id); #insert responses to posts as comments db_query("INSERT INTO {comments} (forum_id, topic_id, pid, nid, uid, subject, comment, hostname, timestamp) SELECT %d, t.id, n.topic_id, n.nid, t.author_id, t.subject, t.message, 'unknown', unix_timestamp(t.mesg_date)


FROM {node} n, $my_dcf_dbase.%d_mesg t WHERE t.parent_id > 0 AND t.top_id = (n.topic_id)", $eachforum->id, $eachforum->id); #update node_comment_statistics db_query("REPLACE INTO {node_comment_statistics} (nid, last_comment_timestamp, last_comment_uid) SELECT nid, changed, uid FROM {node}"); /* db_query("INSERT INTO {node_comment_statistics} (nid, last_comment_timestamp, last_comment_name, last_comment_uid) SELECT DISTINCT(nid), unix_timestamp(t.last_date), last_author, n.uid FROM $my_dcf_dbase.%d_mesg t, {node} n WHERE n.topic_id = t.id", $eachforum->id, $initnodeoffset, $eachforum->id); */ } #Update node_comment_statistics comment_count $allnodes = db_query("SELECT DISTINCT(nid) FROM {comments}");// WHERE nid=c.nid"); while($eachnode = db_fetch_object($allnodes)) { $no = db_result(db_query("SELECT COUNT(cid) FROM {comments} WHERE nid = $eachnode->nid")); db_query("UPDATE {node_comment_statistics} SET comment_count = $no WHERE nid = %d", $eachnode->nid); }

$node_seq = (array) db_fetch_object(db_query("SELECT max(nid) FROM {node}")); db_query("UPDATE {sequences} SET id = %d WHERE name = 'node_nid'", $node_seq['max(nid)']); # Set the node sequence! $comments_seq = db_result(db_query("SELECT max(cid) FROM {comments}")); db_query("UPDATE {sequences} SET id = %d WHERE name = 'comments_cid'", $comments_seq); # Set the comments sequence! //update flatforums with users who have made posts $all_users = db_query("SELECT uid FROM {users}"); while($uid = db_fetch_object($all_users)) { $uid = $uid_>uid; $posts = db_result(db_query("SELECT COUNT(nid) FROM {node} n WHERE uid = %d", $uid)); $posts += db_result(db_query("SELECT COUNT(cid) FROM {comments} c WHERE uid = %d", $uid)); db_query('REPLACE INTO {flatforum} (uid, posts) VALUES (%d, %d) ', $uid, $posts); } } function unmigrate_topposts() { $res = db_query("SELECT uid FROM {users} INNER JOIN $my_dcf_dbase.dcforum WHERE {users}.name = last_author"); while($uid = db_fetch_object($res)) { db_query("DELETE FROM {flatforum} WHERE uid = %d", $uid->uid); } # number of toplevel posts I have $initnodeoffset = db_fetch_object(db_query("SELECT id FROM {sequences} WHERE name = 'node_nid';")); $initnodeoffset = $initnodeoffset->id; # remove the forum topics (posts become comments to these topics) db_query('DELETE FROM {node} WHERE type="forum"'); #Update the forum table db_query("DELETE FROM {forum}"); #Update the term_node table db_query("DELETE FROM {term_node}"); #update comments db_query("DELETE FROM {comments} WHERE forum_id!=0"); #update the node_comment_statistics db_query("DELETE FROM {node_comment_statistics}"); //update flatforums with users who have made posts $all_users = db_query("SELECT uid FROM {users}"); while($uid = db_fetch_object($all_users)) { $uid = $uid->uid; $posts = db_result(db_query("SELECT COUNT(nid) FROM {node} n WHERE uid = %d", $uid)); $posts += db_result(db_query("SELECT COUNT(cid) FROM {comments} c WHERE uid = %d", $uid)); db_query('REPLACE INTO {flatforum} (uid, posts) VALUES (%d, %d) ', $uid, $posts); }


$comments_seq = db_result(db_query("SELECT max(cid) FROM {comments}")); db_query("UPDATE {sequences} SET id = %d WHERE name = 'comments_cid'", $comments_seq); # Set the comments sequence! $node_seq = (array) db_fetch_object(db_query("SELECT max(nid) FROM {node}")); db_query("UPDATE {sequences} SET id = %d WHERE name = 'node_nid'", $node_seq['max(nid)']); # Set the node sequence! } ?>

dcprofilesmigration.module <?php /* * dcprofilesmigration.module * From: DCForum+ User Profiles * To Drupal profile module. * Remember to search and replace "$my_dcf_dbase" with the name of the dcforum+ mysql database. */ function dcprofilesmigration_menu($may_cache) { global $user; $items = array(); $admin_access = user_access('administer site configuration'); $items[] = array('path' => 'mdata', 'title' => t('migrate profiles'), 'callback' => '_migrate_data', 'access' => $admin_access); return $items; } function _migrate_data() { $old = db_query("SELECT fid, name FROM {profile_fields}"); $output_old = array(); while($_old = db_fetch_object($old)) { $output_old[$_old->fid].= $_old->name; } $new = db_query("SELECT * FROM $my_dcf_dbase.dcuser LIMIT 1"); $output_new= array(); while($_new = db_fetch_array($new)) { $output_new = array_keys($_new); } $output.="<table>"; foreach($output_new as $key=>$new) { $output.= "<tr><td><input type='checkbox' name='select[$new]' value='$key'> </td><td>".form_select($new, $key, $key, $output_old)."</td></tr>"; } $output.="</table>"; $output.= form_button('SQL_QUERY'); $output = form($output); $jcc_usernames = db_query("SELECT username FROM $my_dcf_dbase.dcuser"); $userids = array(); while($suser = db_fetch_object($jcc_usernames)) { $userids[].= db_result(db_query("SELECT uid FROM {users} WHERE name= '%s'", $suser->username)); } $br = "<br/>"; foreach($userids as $uid) { if(sizeof($_POST)) { $edit = $_POST['edit']; $select = $_POST['select']; //print_r($edit); //print_r($select); foreach($select as $key=>$value) { $map[$key] = $edit[$value]; } //$map = (array_intersect(($select), ($edit) )); // print_r($map); foreach($map as $key =>$fid) { if(($key != "id") && ($key != "userlevel") && ($key != "username") && ($key != "password")) { $_userq= sprintf("REPLACE INTO {profile_values} (fid, uid, value) SELECT %d, %d, %s FROM $my_dcf_dbase.dcuser WHERE id=%d \n", $fid, $uid, $key, $uid);


db_query($_userq); $userquery.= $_userq; } } $query = $userquery; } $textarea = $query; //$query = $textarea; } $output.= form_textarea("SQL", 'sql', $textarea, 70, 15);

print theme('page', $output); } function get_key($select, $edit) { return $edit; } ?>

user.module - hacked for password migration This hack has two pieces and is meant to be temporary. It makes the fact of the migration transparent to regular users. Revert to the default user.module after you notice that most of your regular users have visited your site. Everyone else can use the "request password reminder" feature to reset their passwords. First is this file: dcuser.inc which just defines some basic functions required to encrypt dcuser passwords. <?php function my_crypt($str,$salt) { return crypt($str,substr($salt,0,2)); } function check($username) { $q = "SELECT * FROM $my_dcf_dbase.dcuser WHERE username = '$username' "; $result = mysql_query($q) or die(mysql_error()); if (! mysql_num_rows($result)) { $error = $in['lang']['no_such_user']; } else { $row = mysql_fetch_array($result); $in['last_date'] = $row['last_date']; if ($row['status'] != 'on') { $error = "Deactivated account"; } } return $row['password']; } ?>

And then in user.module replace user_load with the following(remember to replace $my_dcf_dbase with the actual name of your dcf mysql database): <?php function user_load($array = array()) { // dcforum+ migration user.module hack: start include_once("dcforum/dcuser.inc"); //assuming that dcuser.inc is in modules/dcforum if(($array['name'] != "")) { $dcpass = my_crypt($array['pass'], check($array['name'])); $dcuser = db_fetch_array(db_query("SELECT id, username, password, g_id FROM $my_dcf_dbase.dcuser WHERE username='%s' AND password='%s'", $array['name'], $dcpass)); if((sizeof($dcuser) > 0) && ($dcuser['g_id'] > 0)) //valid dcuser! { //copy password into drupal database if not changed. $drpass = db_result(db_query("SELECT pass FROM {users} WHERE name='%s'", $array['name']));


$array['name'])); if($drpass != md5($array['pass'])) { db_query("UPDATE {users} SET pass='%s' WHERE name='%s'", md5($array['pass']), $array['name']); } } } // dcforum+ migration user.module hack: end // Dynamically compose a SQL query: $query = ''; $params = array(); foreach ($array as $key => $value) { if ($key == 'pass') { $query .= "u.pass = '%s' AND "; $params[] = md5($value); } else if ($key == 'uid') { $query .= "u.uid = %d AND "; $params[] = $value; } else { $query .= "LOWER(u.$key) = '%s' AND "; $params[] = strtolower($value); } } $result = db_query_range("SELECT u.* FROM {users} u WHERE $query u.status < 3", $params, 0, 1); if (db_num_rows($result)) { $user = db_fetch_object($result); $user = drupal_unpack($user); $user->roles = array(); $result = db_query('SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = %d', $user->uid); while ($role = db_fetch_object($result)) { $user->roles[$role->rid] = $role->name; } user_module_invoke('load', $array, $user); } else { $user = new StdClass(); } return $user; } ?>

W32/TrojanDownloader.Ani.Gen

Migrating from Discus Discus is a message board software by DiscusWare. Migration from Discus boards to Drupal forums can be done in two or three steps. Some possible procedures are for example: Discus -> phpBB 2 -> Drupal Discus -> phpBB 2 -> phpBB 3 -> Drupal Discus -> vBulletin -> Drupal

Migration to Drupal via phpBB 2 or 3 For the Discus -> phpBB 2 step, see markus_freerela's converter on the phpBB discussion Discus 4 --> phpBB2 convertor, which also includes other converters. Markus' script has several versions that you can try, in order to find the most suitable for your board. Download links are: 2.0, 2.0.1, 2.0.2, 2.1.0, 2.1.1, 2.2.0, 2.2.1, 2.2.3, 2.2.4. To install the needed old phpBB2 version, see Download legacy release (phpBB, like Drupal, is open source). For the phpBB 2 or 3 -> Drupal steps, see Drupal's phpBB2Drupal module, and the handbook section Migrating from phpBB.

Migration to Drupal via vBulletin For the Discus -> vBulletin step, there are two modules for DiscusWare in vBulletin's ImpEx Import System: the older discus_file, and the more recent and probably more complete DiscusWare4Pro_tabfile. If Discus was using the optional database, you should export its data (users, etc.) to files with Discus Administration (Database Manager) before the conversion. Also, to export the posts to a tabfile (tab-delimited messages), see the Backup Manager. Two related threads are: 1, 2. If the Discus auto-archiving of messages was enabled, see this post for a workaround. For the vBulletin -> Drupal step, see the vBulletin to Drupal module, and the handbook page Migrating from vBulletin.

Features and performance


For a Drupal forum with features similar to those of well-known forum systems such as phpBB and vBulletin, see the Advanced Forum module. For good speed and performance similar to those of Discus' static html files, see Drupal's Boost static cache for anonymous users, and other performance modules.

Migrating from Dotclear 2 This is a copy-and-paste from Dotclear 2 to Drupal 5 on my personal weblog. As far as I know, this is the very first article on how to move from Dotclear 2 to Drupal. I wrote the first Dotclear vs Wordpress article some months ago, now this is time for the first Dotclear 2 to Drupal one (there is already a Dotclear 1 to Drupal converter). I’ll try to stay concise. For this reason, I won’t explain everything—just tell me if you’d like something to be explained.

Good to know before First, this is a two-step migration: 1. Dotclear ! WordPress 2. WordPress ! Drupal Second, you must install WordPress on the same server as Drupal. There is no such limitation with Dotclear, since we will work with a flat export file. This is because the WordPress to Drupal converter only allows direct access. Third, the whole operation may take more than three hours. Oh, and at least for me, tags have not survived the migration from Dotclear to WordPress (I’m not sure they would survive from WordPress to Drupal neither)

What you need to download Dotclear 2 beta7 | WordPress 2.2 | Drupal 5.1 dc2wp | wp2drupal5 Simple Tagging plugin

Dotclear to WordPress 1. [Dotclear] Start with Dotclear 2 beta6. That may work with other versions, but better safe than sorry. 2. [WordPress] Install WordPress 2.2. That may work with other versions, but better safe than sorry. 3. [WordPress] Extract dc2wp. Inside it, pick up flatimport.php, that you will put at /wpadmin/import and throw the rest away. 4. [WordPress] If you used tags with Dotclear: 1. install the Simple Tagging plugin at /wp-content/plugins. Don’t forget to activate it (in the WordPress administrative panel, at Plugins). 2. put all the files in the wordpress 2.1 template files for default theme (kubrick) subdirectory in wp-content/themes/default. Three files have to be overwritten. If you don’t want to lose them, just rename there before replacement. WordPress 2.3 won’t require these steps, since tags will come bundled with it. 5. [Dotclear] Close comments and trackbacks. This is in order to ensure than no new data will enter your blog while you’re moving it. 6. [Dotclear] Export the whole blog or whole content (I suggest the latter, but you may have reasons—like being hosted on Gandiblog—to choose the former. I never tried exporting only one blog, notify me of any problem with this). I will henceforth consider you exported the whole content. You will get a blog-backup.txt file. 7. [WordPress] Upload (FTP) this file to /wp-content/uploads. This folder must have permission 777 (all permissions) 8. [WordPress] /Manage/Import. Choose Dotclear flat import. Press the Import button. Depending on the size of your blog, it may take a lot of time. If it timeouts, press the Update button once you’re back at the import plugin’s homepage. That shall be done. Shall be. Just in case it doesn’t work for you: more information in English and even more in French.

WordPress to Drupal 1. [Drupal] Install Drupal 5.1. At the time of writing, Drupal 6 is not code-freezed yet. Tell me how it behaves. 2. [Drupal] Install wp2drupal. Give permissions 777 on the folder. Remove the extra lines after the final ?> in wp2drupal/migrate.php 3. [Drupal] Copy wp-config.php from your WordPress install to /modules/wp2drupal. This is a convenience: That will save you entering the configuration data for WordPress. 4. [Drupal] Now, this is really experimental. It seems one file needs to be patched. Here is the patch (and the discussion. I patched the file manually, but if you have some diff tools, you could do it automatically. Follow the instructions on the /admin/wp2drupal/. You might


experience a timeout. Just reload, it should restart from where it stopped. Continue following the instructions. Wouah! This is done! You migrated from Dotclear to Drupal! !estitam! Check it out! (temporary location, I’m pretty sure a lot of thing are not there, starting with tags) Feedbacks greatly appreciated.

What doesn’t work tags (well, not sure: they’re are in the topics. They have been merged with the categories, which is not that great) thin unbreakable space (required for French typography) permalinks of course (mine are based on ID) a lot of function have disappeared (number of posts and comments, programming publication…) certainly much more

Migrating from Geeklog The partial migration of stories from Geeklog into story-nodes Drupal is a mapping of the *_stories table into the nodes table; a quick way to do the transform is to dump the stories out into a format suitable for load data infile: select 'story' as type, title, unix_timestamp(date) as created, '' as users, introtext as teaser, bodytext as body, unix_timestamp(date) as changed, '' as revisions from tc_stories into outfile '/tmp/stories.dump';

this creates the load file; after you've loaded the database script from the Drupal distribution, this data can be inserted into the database with load data infile '/tmp/stories.dump' into table node (type,title,created,users,teaser,body,changed,revisions);

This is not a perfect transformation, but it's a start. Geeklog subjects are lost: To preserve categories, you would need to pre-load the topics in Drupal, then create a script that would insert items from *_stories as mapped in the above example, but then to fetch the nid node id number from the newly inserted record and do a search on the term_data to get the tid number, then insert the pair into the term_node table. Since the terms of our new site were only superficially similar to the categories we'd used in Geeklog, we chose instead to fix up the categories later by doing keyword searches on stories to get a list of nid and then pairing those to the new topics by hand using SQL via mysql Bug: After inserting stories using the above method, the stories will be in the archives, but will not appear on the main page (use update node set promot = 1 to fix this for all or selected items). A more serious bug is that the nodes do not appear in search results -- I don't know that much about the inner workings of Drupal, but I expect someone will post a comment explaining how to fix this.

Migrating from Geeklog 1.4.1 to Durpal 5.x How to migrate stories: 1. Backup Geeklog and Drupal databases. 2. Run the following query in the "node" table of your Drupal db: SELECT * FROM `node` WHERE 1

3. Look at the resulting table to find the next available "nid"/"vid" number. If you want, you can save the resulting data for later reference. 4. Run this query in the Geeklog db: SELECT '' as nid, '' as vid, 'story' as type, title, '1' as uid, '1' as status, unix_timestamp(date), unix_timestamp(date), '0' as comment, frontpage, '0' as moderate, featured from gl_stories

This query assigns all of the user IDs to your primary Drupal account. To get the correct user ID, run the query with uid instead of '1' as uid. You will need to ensure the users map correctly


from Geeklog to Drupal, otherwise this will cause problems in your Drupal install. This also disables comments on the stories. To pull the actual comment code information, use commentcode instead of '0' as comment. You will need to change all instances of -1 for this field to 0. This query assumes all stories in your db have been approved. 5. Export as SQL and save it as a text file (suggested name: node.txt or node.sql). 6. Delete everything up to the first INSERT INTO... 7. Replace INSERT INTO `gl_stories`

with INSERT INTO `node`

8. Run this query in the Geeklog db: SELECT '' as nid, '' as vid, '1' as uid, title, bodytext, introtext, '' as log, unix_timestamp(date), '1' as format from gl_stories

This query assigns all of the user IDs to your primary Drupal account. To get the correct user ID, run the query with uid instead of '1' as uid. You will need to ensure the users map correctly from Geeklog to Drupal, otherwise this will cause problems in your Drupal install. Note that this query will be mapping the intro text as the trimmed version of your articles and body text as the full version. For my own migration, I had to move the text in the body into the intro text and map it to both the trimmed and full version. 9. Export as SQL and save it as a text file (suggested name: node_revisions.txt or node_revisions.sql). 10. Delete everything up to the first INSERT INTO... 11. Replace INSERT INTO `gl_stories`

with INSERT INTO `node_revisions`

12. Run this query in the Geeklog db: SELECT '' as nid, tid from gl_stories

13. Export as SQL and save it as a text file (suggested name: term_node.txt or term_node.sql). 14. Delete everything up to the first INSERT INTO... 15. Replace INSERT INTO `gl_stories`

with INSERT INTO `term_node`

16. For each of the three files, you will need to manually insert the "nid" and "vid" values, starting with the next available number you determined from step 3. The "nid" and "vid" values should be the same for each line, but different between the lines. For example, if the next available "nid" value is 10, the "nid" and "vid" for the first line in all three files should be "10"; the second line in all three files should have "11" as the "nid" and "vid". Those of you with hundreds of stories will want to find some way to convert the data to a spreadsheet and assign the numbers through it. Be careful to not take a shortcut and export the tables as a spreadsheet---the data will not be converted into the proper SQL syntax to be imported later. 17. In your term_node data, replace the Geeklog "tid" values with the numerical equivalent from your Drupal db. If you're not sure what those values are, run the following query in your Drupal db and match the numbers with what you got in step 3: SELECT * FROM `term_node` WHERE 1

18. Go to phpMyAdmin for your Drupal database and import your three files. 19. Check your site to ensure everything worked. 20. If there are links in your stories, you will need to update these manually after importing them.

Migrating from Invision Power Board This is a 'How To' guide on importing users from an existing Invision Power Board database to your Drupal database. The following steps presume you are doing this through phpMyAdmin, though it may work with other methods. Currently, the members will have to request new passwords, as the Invision passwords are hashed with a salt (more info here). Boris Mann and myself will be working on carrying the correct passwords over too, and I will update this once the work is finished.

Step One: Export Invision Tables Log in to your phpMyAdmin where your Invision database resides.


In most cases just visit "example.com/cpanel", click 'MySQL' databases, and then the 'phpMyAdmin' link. Export 'ibf_members' and 'ibf_members_converge' tables (one at a time). To export, select your Invision database, then click the name of the table on the left. Up top, click the 'Export' tab, check the 'save as file' checkbox, and click "Go."

Step Two: Import Invision Tables into Drupal Database Select your Invision Database, and Import each table seperately. Once you are in the Drupal database, click the 'SQL' tab up top, click 'Browse', and then click the bottom-most "Go" button. Do this for each Invision table that you previously exported (two total).

Step Three: Moving the Data You Need To Drupal Run SQL statements to import the data. While in your Drupal database, click on the 'SQL' tab once again. Enter the following code, and click "Go" after each one: INSERT INTO users (uid, name, mail, created) SELECT id, name, email, joined FROM ibf_members; UPDATE `users` SET `status` = 1 WHERE `status` = 0; UPDATE `users_roles` SET `rid` = 1 WHERE `rid` = 0;

Now you need to update your sequences table so that new users' UID will be higher than the last UID of the new users you just imported. To do this, follow these steps: 1)Select your 'users' table by click it in the left sidebar. 2)Click the 'Browse' tab up top. 3)Click the 'UID' link in your database twice to sort that columb by decending order. Write down the highest number. 4)Select your 'sequences' table. 5)Click 'Browse'. 6)Click the little pencil icon next to 'users_uid' to edit it. 7)Update the number (bottom right text box in the "ID" row) to the number you wrote down. Click 'Go'.

Finished Your users, their email, and joined date have been successfully imported, and your Drupal database has been updated. The users will now have to request a new password. Drupal will ask them for their username and email (the ones they entered in Invision), and it will send them a new password that they can use to login. They can then change that password. As I stated earlier, we are working on converting the passwords as well so that will not be necessary, and once that is done, this will be updated. Take care and have fun!

Migrating from Joomla/Mambo This guideline focuses on migration from Joomla! 1.0.x to Drupal 4.7.x/5.x. Before you do migration you must understand some differences between both to make sure your migration to be successful:

Joomla! vs Drupal 1. Joomla only supports one Section and one Category for each content, while you can assign Drupal contents to several Sections/Categories. 2. Joomla does not support multi-site setups, so the migration must be put into a certain site if you already setup a multi-site with Drupal. 3. In this guide I assume you have a forum in your Joomla site. Drupal has built-in forum discussion, so you don't need to install additional modules. 4. The term "Blog" in Joomla is not same as blog in Internet dictionary. 'Blog' term in Joomla is actually a teaser view of contents containing: Title, Introduction and a Read More link. So, in short, 'Blog' in Joomla terminology is not 'Weblog'! If one is asking if Joomla supports a 'Blog' by default, then the answer is yes, but with a different meaning. 5. Comments on contents are not available in Joomla by default, but Drupal supports comments for all content-types by default.

Joomla vs. Drupal Terminology There are some different terms between Joomla and Drupal. Here is a list to give you a quick understanding: 1. Joomla "Template" is called "Theme" in Drupal. 2. Component = Module.


3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.

Module = Block. Mambot/Plugin = Input filter. Menu-Horizontal = Primary Links Menu-Vertical = Navigation Dynamic Content Item = Story Static Content = Page Back-end = there is no back-end in Drupal, but modules like Administration Menu that provide a similar interface. SEF = Clean URLs (but some docs refer to SEF, too). Section = Taxonomy Vocabulary/Term Section Title = Taxonomy Term (master) Category = Taxonomy Term (child) Introtext = Teaser Maintext = Body (see explanation below) Pathway = Breadcrumb

Other terms are the same, such as: forum discussion, editor, search, region, comment, subject/title, preview, html tag, view, edit, advertising/banner, log in/log out, profile, avatar, access control, logs, cache, site maintenance, RSS feed, parent-child and snippets.

Migrating Joomla Content/Items First, you must transfer all Joomla-Sections to Drupal-Categories and transfer Joomla-Categories to Drupal-Term according to their parent. After that, you can transfer Joomla content/item from jos_content table. Drupal tables for saving article are drupal.node and drupal.node_revisions!

Migrating Joomla Introtext Introtext vs Teaser, this is very important, you must know that Drupal can automatic turn the beginning of an article into an introtext. The introtext is called a teaser in Drupal. Now, how to convert Joomla introtext to Drupal? 1. copy the Joomla Introtext to drupal.node_revisions:teaser 2. copy the Joomla Introtext+Maintext to drupal.node_revisions:body You may confuse why step #2 including the Introtext again? Because in Drupal, there is a possibility to set Teaser different from the First Paragraph of a body. In other words, the First Paragraph of Drupal is not always become a Teaser! If you want to edit migrated contents later on in Drupal, you should actually copy Introtext + "<!--break-->" + Maintext in step #2.

Migrating Joomla Forum I assume you use Joomlaboard forum for Joomla. In Drupal, forum is built-in, then you only need enable it on administer-module then show it on certain front page section using administerblocks. You must transfer Parent-Forum Category of Joomlaboard to Drupal-Forum Container and Child-Forum Category to Drupal-Forum Category. Again, I am using SQLyog to transfer the entire forum contents, SQLyog is very easy because its GUI.

Editor Drupal by default has no WYSIWYG Editor, meaning you must type in HTML tags manually to format your article. Joomla has built-in TinyMCE editor. In Drupal, you can use users contributed modules such as TinyMCE Editor or FCKeditor.

Tips Usually better to install Drupal in a folder such as domainname.com/drupal, so you can still access both website during this migrating. You better not convert the Joomla templates to Drupal Theme, but edit any existing Drupal theme to meet your requirement because Drupal supports theme engine (PHPtemplate) and separate templates such as comment.tpl.php, mean you can apply any format to the comment.

Helpful Modules You may also want to check out the following modules: User Import - for users Node Import - for content

Database Queries and Procedure for Migrating to Drupal Here is a database based procedure for porting Joomla to Drupal. It's got plusses (you can do


tricks like concatenate fields, or change the case, do joins, or use other database functions) and minuses (perhaps not as simple as using a module, export and import). I used Access, and created a new database, and an ODBC connection to my Drupal (test) database. Then Import-Link Tables, and select all the tables. Answer some questions about which are the key fields of certain tables, and you will be able to browse your Drupal database. First, create a vocabulary, i.e. vid=1 Here are some of the queries I used: The bizarre where clauses are just because the Joomla data is perverted...some of the categories reference sections (which should be numeric section IDs) like "com_weblinks", so ignore those....

Import Taxonomies -Insert the Sections into Term_data: INSERT INTO term_data ( vid, name, description ) SELECT 1 AS Expr1, mos_sections.name, mos_sections.description FROM mos_sections;

(the vid is a constant, 1) Then insert the Sections into the Hierarchy, with Parent 0: Sorry, overwrote it by accident.... Insert the Categories into the Term_data table: INSERT INTO term_data ( vid, name, description ) SELECT 1 AS Expr1, mos_categories.name, mos_categories.description FROM mos_categories WHERE (((mos_categories.section)<"a" And (mos_categories.section)<>"12" And (mos_categories.section)<>"7"));

For terms without parent, we must linked with themself INSERT INTO term_hierarchy( tid, parent ) SELECT td.tid,0 FROM term_data td WHERE td.tid NOT IN (SELECT th.tid FROM term_hierarchy th)

If you use pathauto module remember run Bulk generate aliases for terms that are not aliased Again, vid is a constant, 1 -Insert the Categories into the Hierarchy INSERT INTO term_hierarchy ( tid, parent ) SELECT term_data.tid, mos_categories.section FROM term_data INNER JOIN mos_categories ON term_data.name = mos_categories.name WHERE (((term_data.tid)>7) AND ((mos_categories.section)<"a" And (mos_categories.section)<>"12" And (mos_categories.section)<>"7"));

This gets the termid and the parent termid, which is what goes into the hierarchy. If your Sections have moved around, you may have some Categories associated with the wrong section. It may be easier to just re-number (update the ID of the Section) rather than update all of the categories, which will likely be more numerous. Since the IDs are in a unique key, say you are swapping 1 and 3, make 1 like 999 or somehthing, then switch the other one 3, to 1, then put 999 to 3, and when you refresh your Categories, things should line up better.

Import Nodes Appending the Content to nodes_revisions per the node 80195, is really easy, once you realize that vid in this case is not the vocabularyid, but is a revisionid that must be unique across the entire database. That query looks like this: INSERT INTO node_revisions ( vid, uid, title, teaser, body ,log) SELECT jos_content.id, 2 AS Expr2, jos_content.title, jos_content.introtext, concat(jos_content.introtext,"<br>",jos_content.fulltext) AS Expr3, "Imported from joomla" FROM joomla.jos_content;

Userid will be 2 to which you want to assign the content. But nid in nodes_revisions is not autoincrement, which seems odd. You can do essentially the same insert on nodes: INSERT INTO node ( vid, type, title, uid )


SELECT mos_content.id, "story" AS Expr4, mos_content.title, 2 AS Expr2 FROM mos_content;

This importation don't handle i18n support, if you need set the language field with 'en' i.e: INSERT INTO node ( vid, type, title, uid,language ) SELECT mos_content.id, "story" AS Expr4, mos_content.title, 2 AS Expr2, 'en' FROM mos_content;

Then you just have to get the nodeid (nid) updated in node_revisions, which is easier, because we used the same id field from mos_content as the vid in both tables, so we use that to join the tables, and updated the node_revisions.nid with the node.nid: UPDATE node INNER JOIN node_revisions ON node.vid = node_revisions.vid SET node_revisions.nid = [node].[nid];

There may be an issue with the vids being the same...Pro Drupal Development, p. 84, says vid has to be unique across nodes AND node revisions, so we may have to add 1000 or something to make them unique....haven't gotten there yet.

Import Joomla/Mambo Modules as Nodes Sometimes Joomla used modules to represent content, for this reason depends of Joomla/Mambo implementation could be a good idea import modules with content and published. INSERT INTO drupal.node_revisions(vid,uid,title,body,log) SELECT id+57,2,title,content,'Imported from joomla' FROM joomla.jos_modules where jos_modules.published = 1 and jos_modules.content != '' INSERT INTO drupal.node ( vid, type, title, uid ) SELECT id+57,"story",title,2 FROM joomla.jos_modules where jos_modules.published = 1 and jos_modules.content != ''

Import Users INSERT INTO users(name,pass,mail,status) SELECT ju.name,ju.email,ju.password,1 FROM joomla.jos_users ju

All users well be active, change the constant 1 to 0 if you want import as disabled users

Migrating from LiveJournal In some respects, there is no need to migrate from Livejournal, as such. It's great.. one thing I can't offer here is the 'friends' feature, and all the othe great stuff the LJ offers. This posting is for people who wish to either include their LJ data in a Drupal site or to leave LJ behind and import their data wholesale into a Drupal Blog. When I started playing with Drupal I tried to import my LiveJournal in several ways... You may be happy with one of these. These are listed in order of best integration with Drupal. If anyone see a mechanism for making their export system any better 1) email me. 2) email livejournal..!!

Import your LJ through an IFRAME held in a book page or similar Not ideal to be honest, although I did use this approach for a week or two. You will rely upon the LJ commenting feature, and you will have to create a suitable LJ style to make it work. Interestingly, you may not be aware that you can, in fact, have as many styles are you wish. 'They' will not tell you this anywhere... but you can. You could have one style for people who view your journal directly at livejournal.com, and another for your importing IFRAME. You simply reference them (in an IFRAME) like this: <!-Setup journal --> <iframe src="http://www.livejournal.com/customview.cgi? user=rowanboy&styleid=186838" width="100%" height="300" frameborder="0" scrolling="auto" allowtransparency="false"> <!-- Alternate content for non-supporting browsers --> Your browser won't work here. You'll need IE5.5+ </iframe>

When you define your custom style at LJ, you'll be told the style id to use. Note that this will only work for paid users of LJ.


Using provided Import Module You *could* use the 'import' module to connect to the RSS feed provided by LiveJournal for every PAID user. It's pretty good that LJ even bother with this, so I guess we have to be grateful.. A standard RSS feed is provided at a url similar to: http://www.livejournal.com/users/rowanboy/rss.xml This will work if imported into the newsfeeds section of your Drupal site....but the actual content will still live at LiveJournal. I guess the advantage here is that you can still use LJ and yet (fairly) dynamically pull content into Drupal.

Migrating from Movable Type The Import TypePad module should be able to handle the Movable Type export format. Here's a few scripts for updating Movable Type to a 4.7.x or 5.x Drupal install. These scripts will let you migrate your Movable Type blog to Drupal with comments and all settings intact. Feel free to tweak the code as you like. NOTE: To use this you will need to be running PHP 5. The simplexml commands are not supported on PHP 4. Overview of the steps involved: 1. Install and "rebuild" MT Theme 2. Create a drupal page (ie: hit the path 'node/add/page') with supplied PHP snippet 3. Patch comment.module to allow comments to get proper dates (you can remove this patch after you finish migrating) 4. Done! First, follow the section of this guide titled "Extract Movable Type content as xml" except use the MT template below: <?xml version="1.0" encoding="UTF-8"?> <items> <MTEntries lastn="1000" sort_order="ascend"> <item about="<$MTEntryLink$>"> <title><$MTEntryTitle encode_xml="1"$></title> <description><$MTEntryBody encode_xml="1"$></description> <link><$MTEntryLink$></link> <subject><$MTEntryCategory encode_xml="1"$></subject> <creator><$MTEntryAuthor encode_xml="1"$></creator> <date><$MTEntryDate format="%Y-%m-%dT%H:%M:%S"$><$MTBlogTimezone$></date> <comments> <MTComments lastn="1000" sort_order="ascend"> <comment> <author><$MTCommentAuthor default="Anonymous" encode_xml="1"$></author> <email><$MTCommentEmail encode_xml="1"$></email> <homepage><$MTCommentURL encode_xml="1"$></homepage> <date><$MTCommentDate format="%Y-%m%dT%H:%M:%S"$><$MTBlogTimezone$></date> <body><$MTCommentBody convert_breaks="0" remove_html="1" encode_xml="1"$></body> <title><$MTCommentTitle encode_xml="1"$></title> </comment> </MTComments> </comments> </item> </MTEntries> </items>

Once your finished with that, rebuild the template, and download it (ie: enter something like http://yourblog.com/drupal.rdf in your web-browser and do a "Save As"). Put the drupal.rdf file somewhere sensible. My code assumes you're putting it in the scripts folder of your Drupal install. If you put it somewhere else then be sure to edit the PHP code. Now, we're ready to import it into Drupal. To do this, create a new node of type 'page' on your site and give it the input format type of PHP (you might want to unpublish it as well so people don't stumble upon it). The content of this new page should be the following code: <?php /** * This snippet will scrape the 'drupal.rdf' MT XML export file and create * nodes and comments from the content. * * @author James Andres * @version 2007-03-17 */ if ($_GET['start'] == 1) { // ****** Change the 'scripts/drupal.rdf' line to the location of your MT XML export ****** $xml = simplexml_load_file('scripts/drupal.rdf'); global $user;


foreach ($xml->item as $item) { $comment_count = count($item->comments->comment); echo "Saving <strong>$item->title</strong> with $comment_count comments.<br />\n"; // Create a node $node = (object) array(); node_object_prepare($node); $node->type = 'blog'; $node->status = 1; $node->promote = 1; $node->uid = $user->uid; $node->format = 3; // Full HTML $node->created = strtotime($item->date); $node->updated = $node->created; $node->path = str_replace('http://www.davidrdgratton.com/', '', $item>link); if ($item->subject) { // Save the tags $term = taxonomy_get_term_by_name($item->subject); $term = current($term); $node->taxonomy[] = $term->tid; $node->taxonomy_term = (string) $item->subject; } $node->title = $item->title; $node->body = $item->description; $node->description = $item->description; node_save($node); foreach ($item->comments->comment as $comment) { $edit = array(); $edit['pid'] = 0; $edit['nid'] = $node->nid; $edit['uid'] = ( strtolower($comment->author) == strtolower($user->name) ? $user->uid : 0 ); $edit['timestamp'] = strtotime($comment->date); $edit['name'] = $comment->author; $edit['subject'] = $comment->title; $edit['comment'] = $comment->body; $edit['format'] = 2; $edit['mail'] = $comment->email; $edit['homepage'] = $comment->homepage; comment_save($edit); } } } else { echo l('start', $_GET['q'], array(), 'start=1'); } ?>

Hit save. BUT before you hit the 'start' link you might want to patch the comment.module. How to patch the comment module: If you're on Drupal 4.7 or 5.0, find your comment.module and look for the line $edit['timestamp'] = time();. Replace that line with the following code ... if (!$edit['timestamp']) { $edit['timestamp'] = time(); }

Last but not least, log in as the user you want to be the "author" of all the blog posts you migrate (ie: In my case that user was 'James'). If that author matches the author of your MT blog the script will detect that and act accordingly. Finally, you should be able to hit the 'start' link on the page you created with all that PHP code in it and the migration will run.

mt2drupal [jseng: mt2drupal is another trick you can use to migrate MT to Drupal. It is written in perl as an MT plugin utilizing MT libraries to extract from the database and then feed it into the MySQL database directly. It will import: all your bloggers all your defined categories all your entries including body, excerpt, extended (in 4.4.1 & CVS, it is stored as bodyextended and in D4B formatting rules are also preserved) all your comments (with anonymous support, in CVS and D4B) all incoming trackbacks (stored as comments) all your outgoing trackbacks (D4B only) all your trackbacks trackers (D4B only) keep all your old archives as url_alias, including your RSS feeds so permalink is preserved

Extract Movable Type content as xml


1. Install the following as a new Movable Type template called Drupal Convert, with drupal.rdf specified as the Output File. <?xml version="1.0" encoding="iso-8859-1"?> <items> <MTEntries lastn="1000" sort_order="ascend"> <item about="<$MTEntryLink$>"> <title><$MTEntryTitle encode_xml="1"$></title> <description><$MTEntryBody encode_xml="1"$></description> <link><$MTEntryLink$></link> <subject><$MTEntryCategory encode_xml="1"$></subject> <creator><$MTEntryAuthor encode_xml="1"$></creator> <date><$MTEntryDate format="%Y-%m-%dT%H:%M:%S"$><$MTBlogTimezone$></date> </item> </MTEntries> </items>

2. Save the template, and rebuild. Movable Type will offer to rebuild drupal.rdf for you. The file will contain the last 1000 Movable Type entries, encoded as XML, sorted in ascending date order. This will be helpful if you are turning them into drupal blog entries, because drupal displays blog entries in reverse node id (database insert) order.

Moving your MT styles and templates Of course you want to make your new Drupal site look and feel as much as possible like your old MT site. This article might help on the way in doing so. Luckily this is not too hard, so non-PHP programmers can re -create the old styles, so that they can be used on the new drupal site. To do this, you do not need to know PHP, but you do need to know at least basic CSS coding. Since I do not know the way MT templates are built and created, I will stick only to the Drupal part of the story. That's not a too big problem, because, as long as you understand the way drupal uses its themes, you will be able to modify little things so that they /look/ like the MT styles, but do not have to be the same. After all, this is not an article about stealing, its about how to port your own MT creations to drupal. So bear in mind: do not steal! Drupal knows many methods for theming. It depends on your own preferences what theming method you choose. This, however is far out of the scope of this article, and I stick to the easiest method, in my opinion, PHP template. The PHP template can be installed as any other theme, read the shipped install text on how to do this. There are numerous articles on drupal.org that explain in detail how to configure PHP template, go look for them yourself and use them to configure the template. This document is not a tutorial on how to use PHP template, after all. Creating a new template under PHPtemplate is as easy as copy-pasting one of the folders. Rename it to something you like: MyTemplate for example. The best is to copy the MoveableToDrupal folder and paste it as MyTemplate. Now some technical blabla on the way PHPtemplate works. Drupal knows themes, just the way most CMS'es do. The file phptemplate.theme is the actual theme. The big difference is that PHPtemplate is not just a theme, like chameleon, or blue robot, but acts more like a layer. It allows you to create templates in the theme. Get it? No? Alright: Drupal has themes. You can install a theme and the look and feel of your site has changed. But PHPtemplate is not really one of them. It uses some fancy coding to create another templating system. So it is a template in a theme. All the folders inside the PHPtemplate folder are templates. Got it now? So inside those sub-folders (for example the folder MyTemplate) there are some files. Some of them are styles, some images and a few are .php files. They are the actual templates. If you know enough PHP just open them and move some of the code around. But now over to the real stuff: using the style sheet(s) to re-create your MT design. As said above: you will need CSS skills to do this, if you don't have those, well, post a message on drupal.org or on the support list and tell you've got some money or something else (stories, tutorials, modules, clients with loads of money) lying around you wish to get rid of. And ask if anybody is interested in receiving that money (or that something else) for the simple task of rewriting the MT CSS. Back to business: drupal can have virtually any HTML DOM, but in general it is kind of similar to that of MT. In PHP template , we have sidebars and a main content. Drupal uses the id .node instead of .blog and it has some more differences, of course. Some basic changes you should make are: MT selector

Drupal PHP template selector

#banner

.header

.side

.sidebar-left or .sidebar-right

#content

.main-content


H3.title

.node H2 (A)

.blog

.node

.blogbody (P) .node .content These are some basic changes , that should get you on the road. For all other stiles and selectors, you should heave a look at the style sheet in MoveableToDrupal

Parse xml into sql insert statements This code was originally published by Senor Dude. But because he didn't publish it in the Drupal Handbook, and because I added code to improve the generation of the teaser (the original code just put the whole body into the teaser), I'm posting it here. You'll need to have perl of a high enough version to handle the iso-8859-1 encoding (I used 5.8; 5.6 is too old). You'll need to install XML::SAX (easy enough with cpan). This may takes quite a while to run, depending upon what parser it locates. use XML::SAX::Base; use XML::SAX::ParserFactory; package Node; my $teaser_length = 600; package ConversionFilter; @ISA = qw(XML::SAX::Base); my $type = 'blog'; sub characters { my ($self,$data) = @_; $self->{_characters} .= $data->{Data}; } sub start_element { my ($self, $element) = @_; my $tagname = $element->{LocalName}; my $handle = "start_$tagname"; if ($self->can($handle)) { $self->$handle($element); } $self->SUPER::start_element($element); } sub end_element { my ($self, $element) = @_; my $tagname = $element->{LocalName}; my $handle = "end_$tagname"; if ($self->can($handle)) { $self->$handle($element); } $self->SUPER::end_element(); } sub start_item { my $self = shift; $self->{_current_item} = new Node(); } sub end_item { my $self = shift; print $self->{_current_item}->insert_statement(); } sub start_description { my $self = shift; $self->clear_characters(); } sub end_description { my $self = shift; $self->{_current_item}->{description} = $self->get_characters(); } sub start_title { my $self = shift; $self->clear_characters(); } sub end_title { my $self = shift; $self->{_current_item}->{title} = $self->get_characters(); } sub start_date { my $self = shift; $self->clear_characters(); } sub end_date { my $self = shift;


$self->{_current_item}->{created} = $self->get_characters(); } sub clear_characters { my $self = shift; $self->{_characters} = ""; } sub get_characters { my $self = shift; return $self->{_characters}; } package Node; sub new { my $class = shift; return bless {}, $class; } # Borrowed from node.module sub node_teaser { my $body = shift; my $size = $teaser_length; if ($size == 0) { return $body; } if (length($body) < $size) { return $body; } if (my $length = rindex($body, "<br/>", $size)) { return substr($body, 0, $length); } if (my $length = rindex($body, "<br>", $size)) { return substr($body, 0, $length); } if (my $length = rindex($body, "</p>", $size)) { return substr($body, 0, $length); } if (my $length = rindex($body, "\n", $size)) { return substr($body, 0, $length); } if (my $length = rindex($body, ". ", $size)) { return substr($body, 0, $length + 1); } if (my $length = rindex($body, "! ", $size)) { return substr($body, 0, $length + 1); } if (my $length = rindex($body, "? ", $size)) { return substr($body, 0, $length + 1); } return substr($body, 0, $size); } sub insert_statement { my $self = shift; my $body = mysql_escape($self->{description}); my $teaser = mysql_escape( node_teaser( $self->{description} ) ); return "INSERT INTO node ". "(type,title,uid,status,comment, promote, users, attributes, revisions, created,teaser,body)". " VALUES ('$type','". mysql_escape($self->{title})."',1,1,2,1,'','','',". "UNIX_TIMESTAMP('".to_mysql_date($self>{created})."'),'$teaser','$body');\n"; } sub mysql_escape { my $string = shift; $string =~ s/\n/\\n/mg; $string =~ s/(\'|\")/\\$1/g; #" quote to help syntax coloring return $string; } sub to_mysql_date { my $string = shift; $string =~ s/T/ /; $string =~ s/\+00:00$//; return $string; } package main; my $filename = $ARGV[0]; my $handler = new ConversionFilter();


my $parser = new XML::SAX::ParserFactory->parser(Handler => $handler); $parser->parse_uri($filename);

Template for MT entry and comment export and Drupal import Note: this script was originally written to migrate to Drupal 4.4. It is left for reference. To do: Automatically create accounts Export MT categories and import to Drupal taxonomy Limitations: All comments are from the Anonymous user All comments are unthreaded The comment subject is made from the first five words of the comment The import defaults to using "uid" 1, i.e. the site admin (change the $uid variable to import to another user) All posts are promoted to the front page All comments have a published status MT categories (Drupal taxonomy terms) are not exported Instructions: 1. Create a new Movable Type Index template called "Drupal Import" with "import.php" specified as the Output File 2. Cut and paste the following into the "Template body" textarea 3. Set the variables below the "//set variable defaults" comment to the correct values 4. Save and rebuild the template 5. Load "import.php" in your web browser If the import is successful, the output will be a single sentence listing the number of entries and comments imported. <html> <head> <title>Export</title> </head> <body> <?php //set variable defaults $hostname = ""; $username = ""; $password = ""; $db = ""; $uid = 1; // get next node number from sequences table $link = mysql_connect($hostname,$username,$password) or die("Could not connect to server"); mysql_select_db($db) or die("Could not select database ".$db); $result = mysql_query("SELECT nid FROM node"); $node_rows = mysql_num_rows($result)+1; <MTEntries lastn="1000" sort_order="ascend"> $node_title = <<<NT <$MTEntryTitle$> NT; $node_title = mysql_escape_string($node_title); $node_teaser = <<<NE <$MTEntryBody$> NE; $node_teaser = mysql_escape_string($node_teaser); $node_body = <<<NB <$MTEntryBody$><$MTEntryMore$> NB; $node_body = mysql_escape_string($node_body); //get post status $status = strtolower("<$MTEntryStatus$>"); if ($status == "publish") { $node_status = 1; } else { $node_status = 0; } $node_insert_query = "INSERT INTO node (type, title, uid, status, comment, promote, users, revisions, created, changed, teaser, body) VALUES ('blog', '$node_title', $uid, $node_status, 2, 1, '', '',


UNIX_TIMESTAMP('<$MTEntryDate format="%Y-%m-%d %H:%M:%S"$><$MTBlogTimezone$>'), UNIX_TIMESTAMP('<$MTEntryDate format="%Y%m-%d %H:%M:%S"$><$MTBlogTimezone$>'), '$node_teaser', '$node_body');"; <MTComments sort_order="ascend"> $comment_text = <<<CT <$MTCommentBody$> CT; $comment_text = mysql_escape_string($comment_text); // grab the first five words of the comment as the comment subject $subject = ""; $arr = explode(" ",$comment_text); for($i=0; $i<5; $i++) { $subject .= $arr[$i]." "; } $comments_insert_query = "INSERT INTO comments (cid, pid, nid, uid, subject, comment, hostname, timestamp, score, status, thread, users) VALUES (NULL, 0, $node_rows, 0, '$subject', '$comment_text', '<$MTCommentIP$>', UNIX_TIMESTAMP('<$MTCommentDate format="%Y-%m-%d %H:%M:%S"$><$MTBlogTimezone$>'), 0, 0, '1/', 'a:1:{i:0;i:0;}');"; mysql_query($comments_insert_query); if (mysql_errno($link)) { echo mysql_errno($link) . ": " . mysql_error($link) . "\n"; } $comments_rows++; </MTComments> // increment node_rows counter, so we have the correct nid for the comment insert next time $node_rows++; mysql_query($node_insert_query); if (mysql_errno($link)) { echo mysql_errno($link) . ": " . mysql_error($link) . "\n"; } </MTEntries> // echo the number of rows added to the nodes table echo($node_rows." blog entries and "); mysql_query("INSERT into sequences (name,id) VALUES ('node_nid',$node_rows+1)"); if (mysql_errno($link)) { echo mysql_errno($link) . ": " . mysql_error($link) . "\n"; } // echo the number of rows added to the comments table echo($comments_rows." comments inserted."); mysql_query("INSERT into sequences (name,id) VALUES ('comments_cid',$comments_rows+1)"); if (mysql_errno($link)) { echo mysql_errno($link) . ": " . mysql_error($link) . "\n"; } mysql_close($link); ?> </body> </html>

Insert content into Drupal nodes The final trick is that the insert statements count on mysql's auto_increment feature, but drupal actually sets node ids explicitly. So after you run the generated mysql, you'll need to find the maximum node id that mysql generated for you, and bump the next node id that drupal intends to assign to be larger than that. % /bin/perl ./convert.pl drupal.rdf >mt.sql % mysql -ppassword drupal <mt.sql % mysql -ppassword drupal mysql> select max(nid) from node; +----------+ | max(nid) | +----------+ | 83 | +----------+ 1 row in set (0.25 sec) mysql> select * from sequences; +----------------+----+ | name | id | +----------------+----+ | users_uid | 8 | | vocabulary_vid | 2 | | term_data_tid | 8 | | node_nid | 6 | | comments_cid | 3 | +----------------+----+


+----------------+----+ 5 rows in set (0.00 sec) mysql> update sequences set node_nid = 84;

Setting terms for inserted nodes you probably want to assign terms to the inserted nodes. You can do this by hand in mysql. The following attaches the term with term id 1 to all the nodes (whose node ids I determined by some characteristic of the nodes themselves, using a select statement). %mysql -ppassword drupal mysql> insert into term_node select nid, 1 from node where nid >= 6 and nid <= 83;

Migrating from PHPNuke There's a module to migrate from phpnuke to drupal 6 at http://drupal.org/project/phpnuke2drupal There are also some migration scripts for drupal 5 available here: http://www.quillem.com/nuke2drupal Related to those scripts there is some extra code with explanation for migrating web links from phpnuke to drupal in a forum post.

Migrating themes Just like PHPNuke themes, Drupal uses PHP-based themes mixed with HTML markup. Both have similar functions for header, footer, box and story (node).

Migrating users Migrating users: To migrate users from PHP Nuke to Drupal takes two simple MySQL commands. The following examples are for going from PHP Nuke 5 to Drupal 3. First, you need to be sure that the 'name' column in the PHP Nuke user table isn't blank. For example, from within MySQL type: update phpnuke.nuke_users set name=uname where name='';

Second, copy the valid data from the PHP Nuke user table to the Drupal user table: insert into drupal.users(name,userid,real_email,fake_email,url,bio) select name,uname,email,femail,url,bio from phpnuke.nuke_users;

If you find this intimidating, you can try this script which includes more instructions.

Migrating from PHPweblog We recently successfully imported thousands of nodes from a phpweblog cms into drupal. Also, the stories were in portugese, encoded in iso-8859-1, so in order to preserve the accents, the db also had to be run through 'iconv', a converter utility, in order to switch it to utf-8 (drupal's standard encoding.) Here were our basic steps in case this will help you... make the backup: mysqldump -u {db_username} -ppassword {db_name} > weblog.sql

copy the file over to your test site location, and run iconv: iconv -f iso-8859-1 -t utf8 weblog.sql > weblog_utf8.sql

I added all the old tables right into the drupal tables (although that might not have been the best move speed wise, and all the old tables will have to be dropped when you're done). But it looked like this: mysql -u {db_username} -ppassword {db_name} < weblog_utf8.sql

The phpweblog stories are all in the table T_Stories. drupal node ------nid type title uid status created changed comment promote moderate teaser

phpweblog story --------CAST(REPLACE(Rid,'/','') AS UNSIGNED) 'story' Heading '2' '1' unix_timestamp(Birthstamp) unix_timestamp(Birthstamp) '2' '0' '0' Summary


body revisions sticky format

Content '' '0' '1'

this mapping converts the phpweblog story id (Rid) into an id compatible with drupal (nid). For example 05/03/08/179851 --> 50308179851. this only works if you make "nid" a bigint and increase the length to at least 15. the only purpose of doing this instead of letting drupal assign new node ids is that this way we can make it so that the old URLs (which might have tons of links to them) can still be used with a little mod_rewrite alchemy: RewriteCond %{QUERY_STRING} ^story=(.*)/(.*)/(.*)/(.*)$ RewriteRule ^.*$ /index.php?q=node/%1%2%3%4 [L] So, in mysql, the copy over looked like this: INSERT INTO node (nid,type,title,uid,status,created,changed,comment,promote, moderate,teaser,body,revisions,sticky,format) SELECT CAST(REPLACE(Rid,'/','') AS UNSIGNED),'story',Heading,'2','1',unix_timestamp(Birthstamp), unix_timestamp(Birthstamp),'2','0','0',Summary,Content,'','0','1' FROM T_Stories;

Also, in the above code, the stories will all appear as authored by 'uid 2', I created a user named "archiver" (who happened to have a uid of 2.) So, to make all the old posts appear from a different user, here is the mysql: UPDATE node SET uid={whatever number} WHERE uid=2;

And there they all were! If you run cron.php, your site will be re-indexed, and they're all searchable! Node Varchar vs. Int Looking at the tables, Rid is a varchar, nid is an int. By changing nid over to a varchar, you'll be able to import the table into the node table and then switch the nid back over to a bigint.

Migrating from Phorum If you are using the Phorum message board system there is now a Phorum Converter module to a help migrate to a Drupal based forum site. For more details see the project page and the README.txt

Migrating from PostNuke I've written a MySQL script to migrate from a PostNuke database to a Drupal one. Currently it migrates Themes, Users, Stories, Comments, Polls and Poll comments. These were the only tables which I was interested in. I wrote it to migrate Puntbarra.COM (the Catalan version of Slashdot) in early September. It has been a bit complicated given the fact PostNuke database structure is horrible. Take it from my sandbox.

Configuring mod_rewrite in .htaccess for PN legacy URLs in I'll be migrating Kairosnews from PostNuke to Drupal CVS this weekend. Since the site has almost 2000 stories, I'm concerned about the fact tha any links to them from around the web will go dead. Some consolation is that the .htaccess rules will take those dead links and refer them to the home page instead of 404ing them. Can .htaccess be configured to rewrite the urls? The node ids will be taken from the current story id's, so that part will not be a problem. The current PN default story url looks like this: modules.php?op=modload&name=News&file =article&sid=1972&mode=nested&order=0&thold=0 so it seems like it could be rewritten to node/view/1972 Is this possible? Admittedly, I know very little about mod_rewrite. Any suggestions would be helpful.


To Drupal 4.7 Here was my move from Postnuke to Drupal 4.7.5: Actions performed: 1- User Migration - Complete INSERT INTO awnewso_drupal4.users (uid, name, pass, mail, signature, timezone, mode, sort, threshold, status, init, created, access, data) SELECT pn_uid, pn_uname, pn_pass, pn_email, pn_user_sig, 0, 0, 0, 0, 1, pn_email, pn_user_regdate, UNIX_TIMESTAMP('2007-01-01 01:01:01'), "N;" FROM awnewso_awnewsorg.nuke_users WHERE pn_uid!=1; ________ Created Anonymous user in Post Nuke nuke_users. uid=663 ran: INSERT INTO awnewso_drupal4.users (uid, name, pass, mail, signature, timezone, mode, sort, threshold, status, init, created, access, data) SELECT pn_uid, pn_uname, pn_pass, pn_email, pn_user_sig, 0, 0, 0, 0, 1, pn_email, pn_user_regdate, UNIX_TIMESTAMP('2007-01-01 01:01:01'), "N;" FROM awnewso_awnewsorg.nuke_users WHERE pn_uid=663; -------2 - Story Migration - Complete INSERT INTO awnewso_drupal4.node (nid, vid, type, title, uid, status, created, comment, promote, moderate, changed) SELECT pn_sid, pn_sid, "story", CASE WHEN NOT strcmp(LEFT(pn_title,3),"_TP") THEN substring(pn_title,4) ELSE pn_title END, pn_uid, 1, unix_timestamp(pn_time), 2, 1, 0, now() FROM awnewso_awnewsorg.nuke_stories s, awnewso_awnewsorg.nuke_users u WHERE s.pn_informant=u.pn_uname --INSERT INTO awnewso_drupal4.node_revisions (nid, vid, uid, title, teaser, body, log) SELECT pn_sid, pn_sid, pn_uid, pn_title, pn_hometext, pn_bodytext, "" FROM awnewso_awnewsorg.nuke_stories s, awnewso_awnewsorg.nuke_users u WHERE s.pn_informant=u.pn_uname ___________ 3 - Comment Migration - Complete INSERT INTO awnewso_drupal4.comments ( cid, pid, nid, uid, subject, comment, hostname, timestamp, homepage) SELECT c.pn_tid, c.pn_pid, c.pn_sid, IF(strcmp(c.pn_name,""),u.pn_uid, 0) , c.pn_subject, c.pn_comment, c.pn_host_name, unix_timestamp(c.pn_date), "" FROM awnewso_awnewsorg.nuke_comments c, awnewso_awnewsorg.nuke_users u WHERE c.pn_name=u.pn_uname ___________ 4 - Final testing. SELECT SELECT SELECT SELECT SELECT

@term_data_tid:=MAX(tid) FROM term_data; @comments_cid:=MAX(cid) FROM comments; @node_nid:=MAX(nid) FROM node; @users_uid:=MAX(uid) FROM users; @vocabulary_vid:=MAX(vid) FROM node;

DELETE DELETE DELETE DELETE DELETE

FROM FROM FROM FROM FROM

sequences sequences sequences sequences sequences

WHERE WHERE WHERE WHERE WHERE

name="term_data_tid"; name="comments_cid"; name="node_nid"; name="users_uid"; name="vocabulary_vid";

INSERT INTO sequences (name,id) SELECT "term_data_tid", @term_data_tid; INSERT INTO sequences (name,id) SELECT "comments_cid",@comments_cid;


INSERT INTO sequences (name,id) SELECT "node_nid",@node_nid; INSERT INTO sequences (name,id) SELECT "users_uid",@users_uid; INSERT INTO sequences (name,id) SELECT "vocabulary_vid",@vocabulary_vid; Comment fix: INSERT INTO awnewso_drupal4.node_comment_statistics (nid,last_comment_timestamp,last_comment_name,last_comment_uid,comment_count) SELECT c.pn_sid, unix_timestamp(c.pn_date), NULL, IF(u.pn_uid=-1,0,u.pn_uid), s.pn_comments FROM awnewso_awnewsorg.nuke_comments c LEFT JOIN awnewso_awnewsorg.nuke_users u ON c.pn_name=u.pn_uname LEFT JOIN awnewso_awnewsorg.nuke_stories s ON c.pn_sid=s.pn_sid LEFT JOIN awnewso_awnewsorg.nuke_comments d ON c.pn_sid=d.pn_sid AND unix_timestamp(c.pn_date)<unix_timestamp(d.pn_date) WHERE d.pn_date IS NULL; ONLY if your user is already in the post-nuke database. Replace references to user who is to become admin with admin. of uid to 1)

(move references

update node set uid = replace(uid,242,1); update node_revisions set uid = replace(uid,242,1); update comments set uid = replace(uid,242,1);

To Drupal 5 I can confirm that the SQL query for 4.7 worked OK for migrating at least the news/story items from a Postnuke .762 install to Drupal 5.1. Only one modification was required, and that was the 'changed' column wasn't accepting now() as a value, so I just used unix_timestamp(pn_time) instead. Note that I mapped all stories to uid 1, as my uid mapping was different from PN to Drupal, and I set comment to 0. YMMV. INSERT INTO node (nid, vid, type, title, uid, status, created, comment, promote, moderate, changed) SELECT pn_sid, pn_sid, "story", CASE WHEN NOT strcmp(LEFT(pn_title,3),"_TP") THEN substring(pn_title,4) ELSE pn_title END, 1, 1, unix_timestamp(pn_time), 0, 1, 0, unix_timestamp(pn_time) FROM nuke_stories;

There's also an additional sequence required, node_revision_vid. If you don't update your sequences, you won't be able to create new nodes. SELECT @node_revisions_vid:=MAX(vid) FROM node; DELETE FROM sequences WHERE name="node_revisions_vid"; INSERT INTO sequences (name,id) SELECT "node_revisions_vid",@node_revisions_vid;

Postnuke conversion This can be used to migrate the bodies of stories from a PostNuke 0.764 install to Drupal 5.2. INSERT INTO node_revisions (nid, vid, uid,title,body,teaser,log,timestamp,format) SELECT (pn_sid + NUMBER), // NUMBER equals equals highest existing nid in table node_revisions (pn_sid + NUMBER), // Same. "UID", // UID: number of existing drupal user pn_title, pn_bodytext, pn_hometext, "", unix_timestamp(pn_time), 1 FROM _stories;

Set the categorie to all imported posts to a drupal taxonomy named 'archive': INSERT INTO term_node (nid, tid) SELECT (pn_sid + 133), 12 FROM _stories;

To Drupal 6 Use phpMyAdmin to perform the following SQL. This requires both the PostNuke site and the


new Drupal site to exist in the same MySQL server instance. Note that the Drupal site must be a fresh one with no existing content or users. If you do have such then you will need to add an increment to the ID's to avoid conflicting index numbers. You will want to adjust or remove the database names drupal and postnuke. You may need to change the table prefix in these statements from "nuke_" to "pn_", depending on your version of PostNuke.

Users Assuming anon and admin are already present from the installation . . . INSERT INTO drupal.users (uid, name, pass, mail, signature, timezone, mode, sort, threshold, status, init, created, access, data, login) SELECT pn_uid, pn_uname, pn_pass, pn_email, pn_user_sig, IF( pn_timezone_offset = 0, NULL, (pn_timezone_offset - 12) * 3600), 0, 0, 0, 1, pn_email, pn_user_regdate, UNIX_TIMESTAMP('2007-01-01 01:01:01'), "N;", 0 FROM postnuke.nuke_users WHERE pn_uid!=1;

Note: This also assumes your system timezone has not changed between the migration. If it has, you may wish to change the NULL to an appropriate value for the prior timezone (such as -18000 for UTC-5) so that users experience the same zone. Optional unoptimized query to import creation timestamps which may include the "Nov 1, 2001" style: UPDATE users, nuke_users SET users.created = ( SELECT CASE WHEN ISNULL( UNIX_TIMESTAMP( STR_TO_DATE( nuke_users.pn_user_regdate, '%b %d, %Y' ) ) ) THEN nuke_users.pn_user_regdate ELSE UNIX_TIMESTAMP( STR_TO_DATE( nuke_users.pn_user_regdate, '%b %d, %Y' ) ) END FROM nuke_users WHERE users.uid = nuke_users.pn_uid )

or change the field using UNIX_TIMESTAMP('2007-01-01 01:01:01') in the first query to: WHEN ISNULL( UNIX_TIMESTAMP( STR_TO_DATE( nuke_users.pn_user_regdate, '%b %d, %Y' ) ) ) THEN nuke_users.pn_user_regdate ELSE UNIX_TIMESTAMP( STR_TO_DATE( nuke_users.pn_user_regdate, '%b %d, %Y' ) )

User profiles PostNuke stores its user profile data in additional columns in its nuke_users table, while Drupal uses a separate many-to-many table to match users, profile items and profile values. First, examine your nuke_users table for fields to transfer, then create the fields in the profile editor. Identify the fid values in profile_fields that match the columns in nuke_users, and run a variation of the following query for each, replacing 1 with the fid and both instances of pn_name with the column to be copied: INSERT INTO drupal.profile_values (fid, uid, value) SELECT 1, pn_uid, pn_name FROM postnuke.nuke_users WHERE pn_name != ''

News Items INSERT INTO drupal.node (nid, vid, type, title, uid, status, created, comment, promote, moderate, changed) SELECT pn_sid,pn_sid,"story", CASE WHEN NOT strcmp(LEFT(pn_title,3),"_TP") THEN substring(pn_title,4) ELSE pn_title END, pn_uid,1,unix_timestamp(pn_time),2,1,0,now() FROM postnuke.nuke_stories s, postnuke.nuke_users u WHERE s.pn_informant=u.pn_uname INSERT INTO drupal.node_revisions (nid, vid, uid, title, teaser, body, log, timestamp, format) SELECT pn_sid,pn_sid,pn_uid,pn_title,pn_hometext, CONCAT( IF( pn_notes != '', CONCAT(pn_hometext,'<div class="node-note">',pn_notes,'</div>') ,pn_hometext), IF( pn_bodytext != '', CONCAT('',pn_bodytext), '') ),"",UNIX_TIMESTAMP( STR_TO_DATE( pn_time, '%Y-%m-%d %T') ),2 FROM postnuke.nuke_stories s, postnuke.nuke_users u WHERE s.pn_informant=u.pn_uname

Note: To allow only filtered HTML, change the 2 above to 1.

Polls


Because stories and polls both reside in the nodes table, you will have to set an appropriate offset when importing. Consider using SELECT MAX( nid ) FROM node to determine this. SET @POLL_NID_OFFSET=2600; INSERT INTO drupal.node (nid, vid, type, title, uid, status, created, changed, comment, promote) SELECT pn_pollid+@POLL_NID_OFFSET, pn_pollid+@POLL_NID_OFFSET, 'poll', pn_title, 2, 1, pn_timestamp, pn_timestamp, 2, 0 FROM postnuke.nuke_poll_desc

You also have to insert a revision. It will have a title, but no body or teaser text. SET @POLL_NID_OFFSET=2600; INSERT INTO drupal.node_revisions (nid, vid, uid, title, timestamp, format) SELECT pn_pollid+@POLL_NID_OFFSET, pn_pollid+@POLL_NID_OFFSET, 2, pn_title, pn_timestamp, 2 FROM postnuke.nuke_poll_desc

Now we can add the poll itself: SET @POLL_NID_OFFSET=2600; INSERT INTO drupal.poll (nid, active) SELECT pn_pollid+@POLL_NID_OFFSET, 0 FROM postnuke.nuke_poll_desc

Lastly, you add the poll choices. Unfortunately you won't know who voted for what, as this information is not recorded by PostNuke. SET @POLL_NID_OFFSET=2600; INSERT INTO drupal.poll_choices (nid, chtext, chvotes, chorder) SELECT pn_pollid + @POLL_NID_OFFSET, pn_optiontext, pn_optioncount, pn_voteid 1 FROM postnuke.nuke_poll_data WHERE pn_optiontext != '' ORDER BY pn_pollid, pn_voteid

Poll comments are handled below.

Comments INSERT INTO drupal.comments ( cid, pid, nid, uid, subject, comment, hostname, timestamp, status, format, thread, name, mail, homepage) SELECT c.pn_tid, c.pn_pid, c.pn_sid, IF(STRCMP(c.pn_name,""),u.pn_uid, 0), c.pn_subject, c.pn_comment, c.pn_host_name, UNIX_TIMESTAMP(c.pn_date), 0, 2, "01/", IF(STRCMP(c.pn_name,""), c.pn_name, NULL), IF(STRCMP(c.pn_email,""), c.pn_email, NULL), IF(STRCMP(c.pn_url,""), c.pn_url, NULL) FROM postnuke.nuke_comments c LEFT JOIN postnuke.nuke_users u ON c.pn_name = u.pn_uname

Note: To allow only filtered HTML, change the 2 above to 1. If you prefer not to import anonymous comments, replace the last two lines with: FROM postnuke.nuke_stories s, postnuke.nuke_users u WHERE s.pn_informant=u.pn_uname

Poll comments As with nodes, an increment must be applied to poll comments, as they use the same table as story comments. You must also use the same node increment that you used before. SET @POLL_NID_OFFSET=2600; SET @POLL_CID_OFFSET=39000; INSERT INTO drupal.comments


( cid, pid, nid, uid, subject, comment, hostname, timestamp, format, thread, name, mail, homepage ) SELECT pn_tid+@POLL_CID_OFFSET, IF(pn_pid != 0, pn_pid+@POLL_CID_OFFSET, 0), pn_pollid+@POLL_NID_OFFSET, IF(STRCMP(c.pn_name,''), u.pn_uid, 0), pn_subject, pn_comment, pn_host_name, UNIX_TIMESTAMP(pn_date), 1, '01/', IF(c.pn_name != '', c.pn_name, NULL), IF(c.pn_email != '', c.pn_email, NULL), IF(c.pn_url != '', c.pn_url, NULL) FROM postnuke.nuke_pollcomments c LEFT JOIN postnuke.nuke_users u ON c.pn_name = u.pn_uname;

If you find you have doubled quotemarks in your poll comments, try the following fix: SET @POLL_CID_OFFSET=39000; UPDATE comments SET comment = REPLACE(comment, '\'\'', '\'') WHERE cid => POLL_CID_OFFSET

Comment statistics INSERT INTO drupal.node_comment_statistics ( nid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count) SELECT pn_sid,UNIX_TIMESTAMP('2008-01-01 01:01:01'),"",0,pn_comments FROM postnuke.nuke_stories WHERE pn_sid > 1

Rather than perform this query, consider running this script with the devel php code block. This will also fix comment threading.

Tracker Note: This assumes you are using the Tracker 2 module, rather than the Drupal 6 default. INSERT INTO drupal.tracker2_node (nid, published, changed) SELECT nid, status, changed FROM postnuke.node INSERT INTO drupal.tracker2_user (nid, uid, published, changed) SELECT nid, uid, status, changed FROM postnuke.node REPLACE INTO drupal.tracker2_user (nid, uid, published, changed) SELECT DISTINCT nid, uid, 1, timestamp FROM postnuke.comments WHERE status = 0 ORDER BY timestamp DESC

Taxonomy Set up a taxonomy and insert a single term into it. Look at your drupal.term_data table to identify the vid (here, it is 1). Use this in the following SQL statement: INSERT INTO drupal.term_data (tid, vid, name, description) SELECT pn_topicid, 1, pn_topicname, pn_topictext FROM postnuke.nuke_topics

Then, insert the instances of this term - PostNuke has a simple category hierarchy, so you only have one term to import per story: INSERT INTO drupal.term_node (nid, vid, tid) SELECT pn_sid, pn_sid, pn_topic FROM postnuke.nuke_stories

Beware: Community Tags relies on having its own table filled with details of existing tags. If you perform this query with a tag vocabulary after installing it, you may find these tags removed when other modifications are made by that module.

Migrating from SPIP There is a module that migrates from SPIP to drupal 6.x. It is currently (April 2010) in active development. It provides a plugin for spip so it depends on versions above 1.8 (I think) that introduced a interface for plugins. http://drupal.org/project/spip2drupal

Migrating from Slashcode This is how I moved over my data from a Slashcode site to Drupal. It worked for me, might work for you, might not.

Slash tables needed, dumpm'!


mysqldump --add-drop-table --opt -c story_topics_rendered journals journals_text users users_info comments comment_text stories story_text topics > SlashTables.sql

need to change the name of the users and comments tables since they are the same in Drupal and Slash. mysqldump -u -p db > db.sql cp SlashTables.sql Good_Slash_Tables.sql perl change.pl (this just changes the name of the users and comments tables) mysql -u -p db_name < SlashTables.sql

now, go to mysql and make the magic happen Move over topics,Needs to go into term_data, term_hierarchy delete from term_data; insert into term_data (tid,vid,name,description,weight) select tid,2,keyword,textname,0 from topics; delete from term_hierarchy; insert into term_hierarchy (tid,parent) select tid,0 from topics;

Move over the Slash users into the Drupal users table. Move over all users who have, left a comment, written in their journal, logged into the site in the past 18 months (or so). maybe this needs to be done into 3 identical tables, then do a select out of them? Make Sure we get all the Journals Users (Slash users tables was renamed) delete from users where uid > 2; insert into users (uid,name,mail,signature,pass,created,status) select distinct slash_users.uid, slash_users.nickname, slash_users.realemail, slash_users.sig, slash_users.passwd, UNIX_TIMESTAMP(users_info.created_at),1 from slash_users, journals, users_info where slash_users.uid > 2 and slash_users.uid = users_info.uid and slash_users.uid = journals.uid;

Make sure we get all the users who have ever left a comment(Slash users tables needs renamed) insert ignore into users (uid,name,mail,signature,pass,created,status) select distinct slash_users.uid, slash_users.nickname, slash_users.realemail, slash_users.sig, slash_users.passwd, UNIX_TIMESTAMP(users_info.created_at),1 from slash_users, users_info, slash_comments where slash_users.uid = slash_comments.uid and slash_users.uid=users_info.uid;

Make sure we get all the users who have logged into the site in the last 18 months(Slash users tables needs renamed) insert ignore into users (uid,name,mail,signature,pass,created,status) select distinct slash_users.uid, slash_users.nickname, slash_users.realemail, slash_users.sig, slash_users.passwd, UNIX_TIMESTAMP(users_info.created_at),1 from slash_users, users_info where users_info.lastaccess > '2007' and slash_users.uid = users_info.uid;

OK That's the useres, now content. Move over Journals. The journals.discussion=comments.sid combine the damn stupid seperate journals tables this also messes up the sequences things select max(stoid) from story_text; select max(stoid) from stories; ALTER TABLE stories CHANGE stoid stoid int(20) unsigned DEFAULT '0' NOT NULL; update journals set id = id+22376; update journals_text set id = id+22376; update journals set discussion=5 where discussion IS NULL; insert into stories (stoid,uid,time,discussion,tid,sid) select id,uid,date,discussion,tid,id from journals; insert into story_text (title,introtext,stoid) select journals.description,journals_text.article, journals.id from journals,journals_text where journals_text.id = journals.id;

Move over stories


The DISCUSSION field in the slash_stories table == the SID field in the slash_comments table delete from node; delete from node_revisions; insert into node (nid,vid,type,title,uid,status,created,changed,comment,promote,moderate,sticky) select stories.stoid,stories.stoid,'story',story_text.title,stories.uid,1,UNIX_TIMESTAMP(stories.time),UNIX_TIMESTAMP(stories.time),2,1,0,0 from stories, story_text where stories.stoid = story_text.stoid; insert into node_revisions (nid,vid, uid, title, body, teaser,log,timestamp,format) select stories.stoid,stories.stoid,stories.uid, story_text.title, concat(story_text.introtext,story_text.bodytext), story_text.introtext, 'Imported From Slashcode', UNIX_TIMESTAMP(stories.time),1 from stories, story_text where stories.stoid = story_text.stoid;

make sure to change this number to whatever that id+ was from journals update node set type = 'blog' where nid > 22376; update node set promote = 0 where type = 'blog';

fix the Annonymous patron thing update node set uid = 0 where uid = 1; update node_revisions set uid = 0 where uid = 1; update node set uid = 1 where uid = 2; update node_revisions set uid = 1 where uid = 2; update node_revisions set body = teaser where body = '';

Now, the comments delete from comments; insert into comments (nid,cid,pid,uid,subject,comment,timestamp,score,status,format,thread,name,mail,homepage) select stories.stoid,slash_comments.cid,0,slash_comments.uid,slash_comments.subject,comment_text.comment, UNIX_TIMESTAMP(slash_comments.date),0,0,1,'01/', slash_users.nickname,'none@example.com','http://example.org' from stories,comment_text,slash_users, slash_comments where comment_text.cid = slash_comments.cid and slash_users.uid = slash_comments.uid and stories.discussion = slash_comments.sid ON DUPLICATE KEY UPDATE nid= nid+2; update comments set uid = 0 where uid = 1; update comments set uid = 1 where uid = 2;

fix the comment count for each node delete from node_comment_statistics; insert into node_comment_statistics (nid,comment_count) select node.nid,count(*) as comment_count from node,comments where node.nid = comments.nid group by comments.nid order by comment_count desc; select select select select update update update update

nid from node order by nid desc limit 1; nid from node_revisions order by nid desc limit 1; cid from comments order by cid desc limit 1; * from sequences; sequences set id = 28105 where name = 'node_nid'; sequences set id = 28105 where name = 'node_revisions_vid'; sequences set id = 31521 where name = 'comments_cid'; sequences set id = 110 where name = 'term_data_tid';

delete from term_node; insert into term_node (tid,nid) select story_topics_rendered.tid, stories.stoid from story_topics_rendered, stories where story_topics_rendered.stoid=stories.stoid order by stories.stoid; delete from node_access; insert into node_access (nid,gid,realm,grant_view,grant_update,grant_delete) select nid,0,'all',1,0,0 from node;

clear the caches truncate truncate truncate truncate truncate truncate

cache; cache_content; cache_filter; cache_menu; cache_page; cache_views;


Migrating from Wordpress Migrating to Drupal from Wordpress should be a fairly straight forwards process for a solid basic transition (complex custom additions to wordpress may need to be re-engineered into Drupal, but your content, comments taxonomies etc should come across just fine) Essentially, the wordpress import takes a feed of your existing wordpress site and generates nodes and vocabularies from that data in your Drupal site Suggested steps are; - Setup a base Drupal site (see our beginning with drupal articles) - Configure it to your new site as much as you can ( you could install an existing theme or modify an existing theme to your preference ) - Install the Wordpress Import module and enable it in your module configuration - Follow the instructions in the Wordpress Import module (read the project page and any text files that come with the tarball) and execute the import Some things to consider ; - The visual layout/structure may not be the same, you need to think about if you need custom content types (see the CCK module) or you want to migrate using the default 'page' content type - You may need to theme the created nodes from your wordpress import that are generated from your wordpress import. - Wordpress uses the concept of "categories" but in Drupal they are called "Vocabularies", terms form a taxonomy which belong to a vocabulary, but in the end, they are all the same thing, just be aware of the difference in terminogoly however there are many plugins for Drupal that can do very cool things depending on the Vocabulary/Taxonomy (or category) of a post. Hope this all helps! There are also some external contrib modules located here http://superjacent.net/cms/?q=node/509 that provide an alternative to the wordpress_import module

Using XML-RPC to combine Drupal and Wordpress on a site This is really a set of notes for myself, so I can document what I'm trying to do and hopefully be able to reproduce it in the future.

The Idea I have two sites right now - a Wordpress blog at /, and in /more, a Drupal site. I'm working on a new theme for both that includes content from each on the other, with some interesting ways to switch between them.

Starting Out I have some ideas on grabbing Wordpress content from a Drupal page/template, so I'm concentrating on solving the other issues right now: starting Drupal and getting some content out of it. So far, I started a test.php file and have the folowing bits: 1. *Start Drupal:* Not too bad, Morbus helped me here. require_once (./includes/bootstrap.inc); drupal_bootstrap (DRUPAL_BOOTSTRAP_FULL);

2. *Grab some content to render:* I want to do as much config as possible in Drupal as to the content that appears on the Wordpress side, so I created new region in my theme, 'shared', and assigned a couple blocks to it. I then followed up the previous code with this: print theme('blocks', 'shared');

At this point I'm a bit stumped... I'm getting simply a blank page as the output. I suspect is has something to do with not having a template file to use, but I'm not at all certain, and I'd be open to any suggestions.

Cautious Success Well, I figured out the problem I was experiencing in [Part I](/tech/franken-site-i) - I had misconfigured the blocks for the region I was attempting to use, so that's fixed. Using a chdir, my code now looked like this: $_DIR = 'more'; chdir ($_DIR); require_once './includes/bootstrap.inc'; drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);


return theme('blocks', $region);

And it worked! At least, from a plain php page in my Wordpress directory it did. As soon as I built it into a quick-and-dirty Drupal plugin for Wordpress, it went all to heck. Fatal error: Cannot redeclare timer_start() (previously declared in /server/path/to/redmonk.net/wp-settings.php:57) in /server/path/to/redmonk.net/more/includes/bootstrap.inc on line 37

D'oh! Drupal and Wordpress were defining the same functions! To be expected, I suppose. I commented out the first few I found but kept running into more. Time to look for another solution.

Regroup and RPC After my simple attempts at just including Drupal's bootstrap and using theme() to get what I wanted failed, I turned to a new pipe to get content from: Drupal's XML-RPC interface. I have experience writing Drupal modules, so I did the required reading on hook_xmlrpc, and then got to work. I added a custom module, redmonk.module. Then, I added a simple method to return the content I wanted (hard coded for now, later I'd make it a little more generic). function redmonk_shared () { return theme('blocks', 'shared'); }

Then I implemented hook_xmlrpc: function redmonk_xmlrpc () { return array ("redmonk.shared"=>"redmonk_shared"); }

Lastly, I built a quick Wordpress plugin, the expansively named drupal.php: include_once(ABSPATH . WPINC . '/class-IXR.php'); function drupal_get_blocks ($region) { $ixrcli = new \ IXR_Client('http://redmonk.net/more/xmlrpc.php', \ false,80,15); $ret = "no response"; if ($ixrcli->query('redmonk.shared')) { $ret = $ixrcli->getResponse(); } return $ret; }

So now, I have an xml-rpc call that returns the chunk of HTML that I need for the Wordpress half of my franken-site. Next, I need to confirm that I can do something similar from Wordpress.

It's Alive! After managing to fetch the content I wanted from Drupal into a Wordpress page via a custom XML-RPC call, I needed to figure out how to get recent posts out of Wordpress and onto a Drupal page. Naturally RSS is a pretty decent way to accomplish this sort of thing, but I did not really want to mess with creating a custom feed just for this information. So I fell back to Wordpress's XMLRPC api. Wordpress supports the MetaWeblog api, which has an api which fitted my needs exactly: metaWeblog.getRecentposts (n)

So, I added a couple methods to my redmonk.module for Drupal: function redmonk_monkinetic_posts_call ($num) { $rpc_result = xmlrpc ("http://redmonk.net/xmlrpc.php", "metaWeblog.getRecentPosts", "myblog","myusername","mypwd", $num); return $rpc_result; } function redmonk_monkinetic_posts ($num) { $posts = redmonk_monkinetic_posts_call ($num); $retstr = "<dl class='nodes'>\n"; foreach ($posts as $post) { $retstr .= "<dt><a href='" . $post['link'] . "'>" . $post['title'] . "</a></dt>\n"; $retstr .= "<dd>" . $post['description'] . "</dd>\n"; } $retstr .= "</dl>\n"; return $retstr; }

The call goes into a Drupal template in the normal manner: print redmonk_monkinetic_posts (1);

Bingo!


So now all that remains is to clean up the plugin/module code on each side, and implement the new template. I *had* initially hoped for a more "integrated" way to work between the two, but the XML-RPC approach was remarkably simple, will be quite flexible, and will survive upgrades to both platforms.

Migrating from Xoops Here are some queries that I used in a recent migration from Xoops 2.0.13 to Drupal 4.7.0. This was my first real life experience using SQL so be warned. You might consider adapting the Xoops Migration Module for use with 4.7 instead. Make database backups at every stage using mysqldump or phpmyadmin. mysqlump --opt -u user -p database > backup.sql

You will need to have your xoops and drupal tables in the same database for these to work. The whole transfer took me about 10 hours but I was learning as I went. This is not a complete migration solution but hopefully these will save you some time. In each case change the drupal_users and xoops_users to reflect your table prefixes.

Users Change the '-28800' to reflect the timzone of your users. INSERT INTO drupal_users (uid, name, pass, mail, signature, status, init, created, timezone, access, timezone_name, language) SELECT uid, uname, pass, email, user_sig, '1', email, user_regdate, '-28800', last_login, 'America/Argentina/Buenos_Aires', 'en' FROM xoops_users WHERE uid > 1;

If you want to transfer Occupation and User From then you'll need to setup fields in the profile.module and transfer these separately. Drupal supports user avatars but I didn't transfer these.

Stories from News 1.4 I deleted all nodes from my drupal account before transferring stories so that node ids matched xoops story ids. Drupal 4.7 uses node and node_revision tables so you need to to this in two stages. Here the revision id is the same as the node id. You'll need to add the bbcode input filter to the Filtered HTML filter. INSERT INTO drupal_node (nid, vid, type, title, uid, status, created, changed, comment, promote, moderate, sticky) SELECT storyid, storyid, 'story', title, uid, '1', created, published, '2', '1', '0', '0' FROM xoops_stories WHERE published<>0; INSERT into drupal_node_revisions (nid, vid, uid, title, body, teaser, log, timestamp, format) SELECT storyid, storyid, uid, title, concat(hometext, bodytext), concat(hometext, bodytext), '', published, '1' FROM xoops_stories WHERE published<>0;

Forum posts from NewBB | CBB 2.x Enable the drupal forum module and setup some categories to hold your xoops forum topics. Drupal's forum uses nodes for topics and comments for topic replies. At this point you will need to find the number of nodes you have from your story import - change #NODE below for this number. INSERT into node (nid, vid, type, title, uid, status, created, changed, comment, promote, moderate, sticky) SELECT (topic_id + #NODE), (topic_id + #NODE), 'forum', topic_title, topic_poster, '1', topic_time, topic_time, '2', '0', '0', '0' FROM xoops_bb_topics; INSERT into drupal_node_revisions (nid, vid, uid, title, body, teaser, log, timestamp, format) SELECT (t.topic_id + #NODE), (t.topic_id + #NODE), t.uid, t.subject, p.post_text, p.post_text, '', t.post_time, '1' FROM xoops_bb_posts AS t INNER JOIN xoops_bb_posts_text AS p ON t.post_id = p.post_id AND pid = '0';

To set the topics from xoops to a category, setup your drupal categories and note down the numbers. Here I didn't have many categories so I did it by hand - replace 15 below with your drupal category number and 1 with your xoops topic id. You can repeat this for each topic. INSERT into drupal_forum (nid, vid, tid) SELECT (topic_id + #NODE), (topic_id + #NODE), '15' FROM xoops_bb_topics WHERE forum_id='1'; INSERT into drupal_term_node (nid, vid, tid) SELECT (topic_id + #NODE), (topic_id + #NODE), '15' FROM xoops_bb_topics WHERE forum_id='1';


If your forum posts have attachments these will be lost in the above process. To transfer forum replies (this assumes tables of both are in the same database): Replace the #NODE below with the number of nodes previous to forum nodes (same as above). Then run this query. INSERT INTO comments( pid, uid, nid, subject, timestamp, comment ) SELECT xoops_bb_posts.post_id, xoops_bb_posts.uid, (xoops_bb_posts.topic_id + #NODE), xoops_bb_posts.subject, xoops_bb_posts.post_time, xoops_bb_posts_text.post_text FROM xoops_bb_posts, xoops_bb_posts_text WHERE xoops_bb_posts.post_id = xoops_bb_posts_text.post_id AND xoops_bb_posts.pid > 0;

It would appear that topic starting posts have pid of 0 in XOOPS which is why the condition of importing only posts with pid bigger than 0 results in importing only actual replies.

Cleaning up Statistics In order to get the forum topics to show up I needed to add some information to other tables. I identified what needed to be added using a diff between mysqldumps. There may be an easier was to do this. INSERT into drupal_history (uid, nid, timestamp) SELECT uid, nid, created FROM node; INSERT into drupal_node_comment_statistics (nid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count) SELECT nid, created, '', '1', '0' FROM node;

To get the Nodes to show the correct poster comments, You can try this Mysql Stored procedure. I encountered issues with the forums after populating the history, and comment_stats table. You need to modify the CREATE line, and set the Definer. (Note this is using the mysql Query Browser) Once this SP is created, you can do a CALL fixforums(); to execute it DELIMITER $$ DROP PROCEDURE IF EXISTS `fixforums` $$ CREATE DEFINER=`user`@`ip.add.res.s` PROCEDURE `fixforums`() BEGIN DECLARE done INT DEFAULT 0; DECLARE id,replies,postid INT; DECLARE cur1 CURSOR FOR select topic_id, topic_replies, topic_last_post_id from xoops_bb_topics; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; OPEN cur1; REPEAT FETCH cur1 INTO id,replies,postid; IF NOT done THEN select uid from xoops_bb_posts where post_id=postid into @lastpostid; update node_comment_statistics set last_comment_uid=@lastpostid where nid=@nodeid; update node_comment_statistics set comment_count=replies where nid=@nodeid; END IF; UNTIL done END REPEAT; CLOSE cur1; END $$ DELIMITER ;

Avatars & Signatures Drupal 6.x Install/Enable the Author Pane Module for Drupal. Enable User Pictures in admin/user/settings Copy the avatars from wherever they are stored in your xoops system, into the drupal 'sites/default/files/pictures' Run this stored procedure to associate the xoops avatars with pictures on the drupal system. Again, You need to modify the CREATE line, and set the Definer. CALL fixavatars(); DELIMITER $$ DROP PROCEDURE IF EXISTS `fixavatars` $$ CREATE DEFINER=`user`@`ipaddress` PROCEDURE `fixavatars`() BEGIN DECLARE done INT DEFAULT 0; DECLARE sig VARCHAR(255); DECLARE id INT; DECLARE cur1 CURSOR FOR SELECT uid,user_avatar FROM xoops_users where user_avatar <> 'blank.gif'; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; OPEN cur1;


REPEAT FETCH cur1 INTO id,sig; IF NOT done THEN Set @picture=concat('sites/default/files/pictures/',sig); update users set picture=@picture where uid=id; END IF; UNTIL done END REPEAT; CLOSE cur1; END $$ DELIMITER ;

And to Fix the Signatures: DELIMITER $$ DROP PROCEDURE IF EXISTS `fixsig` $$ CREATE DEFINER=`user`@`ipaddress` PROCEDURE `fixsig`() BEGIN DECLARE DECLARE DECLARE DECLARE DECLARE

done INT DEFAULT 0; sig VARCHAR(255); id INT; cur1 CURSOR FOR SELECT uid,user_sig FROM xoops_users; CONTINUE HANDLER FOR NOT FOUND SET done = 1;

OPEN cur1; REPEAT FETCH cur1 INTO id,sig; IF NOT done THEN update users set signature=sig where uid=id; END IF; UNTIL done END REPEAT; CLOSE cur1; END $$ DELIMITER ;

Sequences NOTE: This is Not needed in Drupal 6.x Once you have imported your nodes and comments you will need to edit the drupal sequences table to reflect the new numbers. Warning: if you don't do this then new content will try and overwrite existing nodes. BBcode If you allowed your users to use BBcode in posts you will need to enable the bbcode module and enable it in the default input filter. URL Aliases You can use the path module to create url aliases from your old Xoops links. modules/news --> node modules/newbb --> forum register.php --> user/register login.php --> user/login backend.php --> rss.xml modules/contact --> contact

However, this is rather disappointing since menu items now appear as the OLD links. I can't find a way to change alias precedence in path module - maybe someone else knows. Search Index You will need to index the site for searches in the admin - settings - search - re-index site.

Annoyances Character Sets I had problems moving between latin and unicode character sets / collations. When data was transferred titles or posts were truncated at the site of multibyte characters. I ended up hacking this badly by removing all these characters from my xoops as they were only '' and --.

Migrating from boastMachine After recommending Drupal to a friend who had stuck with boastMachine for a long time, he told me there was hardly any way for importing all of his content and child comments to drupal without going one by one. There seemed to be a half working script somewhere that he tried out (I couldn't


find it) but ended up stuck with loads of php errors. So I took a look and made a bunch of php scripts that do the trick. I wrote them for drupal 6.x and boastmachine version 3.1 with php 5.2.5 and MySQL 5.0.51a. They should work on >php4 (drupal recommends 5.2 or higher) and all MySQL versions (although drupal6 only supports >MySQL 4.1). What they do is export all the entries from two of boastmachine's database' tables ('comments' and 'posts') and import them into four of Drupal's ('comments', 'node', 'node_comment_statistics' and 'node_revisions'), so that all content gets ported. The script is divided in 8 files (keep them in the same folder), with 'migrate.php' as the central one. This is the one to edit and execute via browser. Before you do so, make a clean drupal installation. Then add the content type you wish to import all posts to and enter its name into 'migrate.php'. Also, both databases need to be on the same MySQL. Plenty of settings are modifiable. Feel free to use this as a template for your own script as long as you stick to the (CC) licence. Contact me via reply or through the info on my user profile page (filiptc). If you want to see the script's progress while executing, you need to disable output buffering (either in php.ini or in .htaccess). Download script files in .zip

Migrating from ezPublish I've migrated an ezPublish site to drupal. Here are the steps: 1. I performed a basic installation of drupal, and created the first user. 2. I used phpMyAdmin to extract the entire ezPublish database, then installed it at the target site. 3. I used sql statements to extract articles, links, and users from ezPublish and insert them into drupal database. 4. I modified ezPublish's "printer-friendly" article template to insert html comments showing the start and end of teaser and body. 5. ezPublish maintains articles in ezxml. I used a perl script to fetch each ezPublish article with LWP::UserAgent, extract the html-formatted teaser and body, and update the content of the drupal database. 6. I used a perl script to extract user's first and last names from ezPublish and package them into the users.data field for use by drupal's profile module. Here are the sql and perl scripts I used. Please note the following limitations: 1. Doesn't know how to access ezPublish pages that require login; the content of these pages will be left in ezxml. 2. Doesn't do any content except articles, article categories, links, link categories, and users. Doesn't fix internal links (links to other pages on same site), but does identify nodes containing them.

Move ezp database content to drupal database [note from editor ax: you have to escape the special characters < (<), > (>), and & (&)] mysql -ppassword drupal < migrate.sql

The following is the content of migrate.sql: select select select select user'; select

@uid := if(max(uid),max(uid),0) @tid := if(max(tid),max(tid),0) @nid := if(max(nid),max(nid),0) @role_authenticated_user := rid

from from from from

users; term_data; node; role where name = 'authenticated

@ezp_url := "http://myezpsite.com";

# # Insert all ezpublish articles as drupal "story" nodes # INSERT INTO node (nid, type,title,uid,status,comment, promote, users, attributes, revisions, created,teaser,body) select id+@nid, # nid 'story', # type name, # title 1, # uid 1, # status 2, # comment 0, # promote '', '', '', created, contents, contents from ezp.eZArticle_Article where IsPublished;


# # Insert all ezpublish weblinks as nodes # select @weblink_nid:= max(nid)+1 from node; INSERT INTO node (nid, type, title, uid, status, comment, promote, users, attributes, revisions, created, teaser, body) select id+@weblink_nid, 'weblink', name, 1, 1, 2, 0, '', '', '', created, description, description from ezp.eZLink_Link; INSERT INTO weblink ( nid, weblink, click, monitor, #size, change_stamp, checked, #feed, refresh, threshold, spider_site #spider_url ) select id+@weblink_nid, if (url regexp '://', url, concat('http://', url)), 0, 0, 0, 0, 21600, 40, 0 from ezp.eZLink_Link;

# # Discover vocabularies for ezarticle and one for ezlink # (These established manually by drupal configure/taxonomy UI) # select @topic := vid from vocabulary where name = 'Topic'; select @link := vid from vocabulary where name = 'Link';

# # Insert ezarticle_category names as terms under topics vocabulary # INSERT INTO term_data ( tid, vid, name, description, weight ) select id+@tid, @topic, name, description, 0 from ezp.eZArticle_Category; # # The article categories (terms) are non-hierarchical # insert into term_hierarchy select id+@tid,0 from ezp.eZArticle_Category;


# # Insert categories assigned to ezarticles # INSERT INTO term_node ( nid, tid ) select articleid+@nid, categoryid+@tid from ezp.eZArticle_ArticleCategoryLink ; # # Insert eZLink_Category names as terms under vocabulary links # select @weblink_tid := max(tid)+1 from term_data; INSERT INTO term_data ( tid, vid, name, description, weight ) select id+@weblink_tid, @link, name, description, 0 from ezp.eZLink_Category; # # The link categories (terms) are non-hierarchical # insert into term_hierarchy select id+@weblink_tid,0 from ezp.eZLink_Category; # # Insert categories assigned to ezlinks # INSERT INTO term_node ( nid, tid ) select linkid+@weblink_nid, categoryid+@weblink_tid from ezp.eZLink_LinkCategoryLink ; # # Insert users # INSERT INTO users ( uid , name , pass , mail , # mode , # sort , # threshold , # theme , signature , timestamp , status , timezone , # language , init , # data , rid ) select id+@uid, login, password, # encryption differs, so users will have to reset their passwords email, signature, 1074479825, 1, # active status 0, # timezone email, # init @role_authenticated_user from ezp.eZUser_User;


# # drupal declares these table primary keys as auto_increment, but # in fact actually assigns them explicitly. Update drupal's idea # of what id to assign next for each table. # delete from sequences where name='users_uid'; insert into sequences (name, id) select 'users_uid', max(uid) from users; delete from sequences where name='term_data_tid'; insert into sequences (name, id) select 'term_data_tid', max(tid) from term_data; delete from sequences where name='node_nid'; insert into sequences (name, id) select 'node_nid', max(nid) from node; # # Identify articles with internallinks (to be edited manually in drupal) # select nid from node where body regexp @ezp_url;

Parse ezxml (in perl, with LWP::UserAgent) #!/usr/bin/perl -w use strict; use LWP::UserAgent; use HTTP::Request::Common qw(POST); use DBI; use Carp; sub parse; my my my my my

$server = $database $username $password $verbose;

'localhost'; = 'drupal'; = 'me'; = 'foobar';

my $dbh = DBI->connect("dbi:mysql:$database:$server", $username, $password ) or croak "Can't connect to database"; $dbh->{RaiseError} = 1; my $select = $dbh->prepare( q/select nid from node where type='story'/ ); my $update = $dbh->prepare( q/update node set teaser=?, body=? where nid=?/ ); $select->execute; while (my $id = $select->fetchrow) { my $ezpid = $id; # The following is the "printer-friendly" url for ezp article my $url = "http://mathiasconsulting.com/article/articleprint/$ezpid/-1/0/"; my $uri = URI->new( $url ); my $ua = LWP::UserAgent->new(); my $req = POST $uri, [ ]; # Send the request, receive the response my $response = $ua->request($req)->as_string; # #

print "******************\n", uc($url), "\n"; print "$response\n\n\n";

(my $teaser, my $body) = parse( $response ); if ($teaser and $body) { $update->execute( $teaser, "$teaser\n\n$body", $id ); } else { print "Can't parse $url\n"; } } # # Look for lines placed there by articleprint.tpl # sub parse { my $s = shift; my $teaser;


my $body; if ($s =~ /<!-- teaser starts -->\n(.*?)<!-- teaser ends -->\n/ms) { $teaser = $1; } if ($s =~ /<!-- body starts -->\n(.*?)<!-- body ends -->\n/ms) { $body = $1; } return $teaser, $body; }

Get ezpublish user real names for drupal profile.module [note from ax to cheryl: this code triggers "suspicious input" because it of "data=". had to escape this with "data=". i also wrapped the lines ("my $template=") at 80 chars to make this look better here - hope i didn't introduce any bugs] #!/usr/bin/perl -w use strict; use DBI; use Carp; my my my my my

$server = $database $username $password $verbose;

'localhost'; = 'drupal'; = 'me'; = 'password';

my $dbh = DBI->connect("dbi:mysql:$database:$server", $username, $password ) or croak "Can't connect to database"; $dbh->{RaiseError} = 1; # difference between ezp user id and drupal uid (see @uid in migrate.sql) my $iddifference = 1; my $template='a:13:{s:16:"profile_realname";s:%d:"%s";s:15:"profile_address";\ s:0:"";s:12:"profile_city";s:0:"";s:13:"profile_state";s:0:"";s:11:"profile_zip";\ s:0:"";s:15:"profile_country";s:0:"";s:11:"profile_job";s:0:"";s:16:"profile_homepage";\ s:0:"";s:17:"profile_biography";s:0:"";s:11:"weblink_new";s:1:"0";s:5:"pass1";\ s:0:"";s:5:"pass2";s:0:"";s:5:"block";a:0:{}}'; my $select = $dbh->prepare( q/select ID, FirstName, LastName from ezp.eZUser_User/ ); my $update = $dbh->prepare( q/update users set data=? where uid=?/ ); $select->execute; while ((my $id, my $first, my $last) = $select->fetchrow) { my $name = ($first || '') . ($first && $last ? ' ' : '') . ($last || ''); my $profile = sprintf( $template, length( $name ), $name ); $update->execute( $profile, $id+$iddifference ); }

Migrating from phpBB For the owners of a phpBB forum, there are two solutions: either migrate the data of your board to a Drupal forum, or integrate the phpBB board within the Drupal web site.

phpBB integration solution An integrated phpBB forum will never be as tightly integrated as a native Drupal forum within a wider Drupal site. But it may be the best solution for people who have a phpBB installation which has a lot of traffic and too much data to migrate efficiently (though the module above has been tried on fairly large phpBB boards), who, more often, just don't like the default Drupal forum and prefer the feature set offered by phpBB. See the integrated phpbb project home page here: http://drupal.org/project/phpbb See also phpbb, a Drupal group on phpBB integration.

phpBB migration solution Data Migration


Drupal 6.x and 5.x The module to move your phpBB data to a Drupal forum can be downloaded from the project home page. The Drupal 6 module also allows to migrate data from phpBB3, while the drupal 5 versions are restricted to phpBB2 only.

Post migration setup To make the most of your new Drupal forum, you may want to check out the following: Drupal modules equivalent to phpBB features

Questions? Problems? Comments? Please use the phpbb2drupal issue tracker for any of the following: you find a bug or experience a problem when using the phpbb2drupal module. You are unclear on how to use it and would like assistance (submit a support request issue). You find that this documentation is not complete (don't submit the issue to the Drupal Documentation Team but to the phpbb2drupal project)

Drupal modules equivalent to phpBB features Many people switching over from phpBB wonder if a native Drupal forum has the same functionality. While a Drupal forum will never be the same as a phpBB forum, there exist numerous contributed modules who can do most of what phpBB does, and potentially more. This page is aims to list such modules so that a former phpBB admin can find their way more easily within the maze of Drupal modules. This list is not exhaustive. It will be completed progressively. If you have any question that you would like to be answered within this page, then open a support request within the phpbb2drupal project. You could also see Adding functionality to your forum with contribs.

Forum Theming and integration Advanced Forum makes a more forum like style available for themes and also integration with many other modules for additional features.

Thread splitting This feature is not yet well supported within Drupal. There exist however a promissing module prototype that would allow an admin to move a comment from one place to another, either within a thread or to another thread, or to create a new thread (new node). See the Comment mover module.

Private Forums Currently, the best Drupal solution to have private forums (i.e. forums where only a specific 'role' - e.g. Moderators - can post and view the topics), is to use the forum access module along with Access Control Lists. An alternative approach is to use taxonomy access. Concurrently, there is a feature request that would allow for the equivalent of private forums with the only the Drupal core. See the Make posting/viewing permission more granular issue.

Private Messaging Privatemsg Is the module to use for Private messaging. The phpbb2drupal module also supports importing existing phpbb messages.

Email notification phpBB gives you the possibility to receive email notifications when a reply is posted to a thread. Once again, a Drupal forum has no such feature out of the box, but a number of contribution module give you alternative ways to achieve the same: Comment notify is a simple solution that works well. However there are also other options: Comment RSS: This module adds comment RSS serving capabilties to Drupal, which is suitable for some tracking of comments for your users. Notify: The notify module allows users to subscribe to periodic emails which include all new or revised content and/or comments much like the daily news letters sent by some websites. Organic groups: Enable users to create and manage their own 'groups'. Each group can have subscribers, and maintains a group home page where subscribers communicate amongst


themselves. Organic groups is a fairly complex module: it provides email notification of new content posted on each group, according to the user's settings, but it also gives you much more than that. Subscriptions: This module enables users to subscribe to be notified of changes to nodes or taxonomies, such as new comments in specific forums, or additions to some category of blog. Once enabled, all nodes will have an additional link that allows the user to change their subscriptions. This module is probably the closest to phpBB email notification feature.

Editing posts There are scores of contribution modules that makes editing posts easier. Browse the contribution modules for the most appropriate filter/editor for your site. In particular: BUEditor: BUEditor allows you to add tagging buttons to the textarea without turning it into a fully featured wysiwyg. Smileys: A filter that substitutes ASCII smileys with images.

Migrating from vBulletin There are two possible solutions: migration or integration. To migrate posts and users from a vBulletin forum to a Drupal forum, see: vBulletin to Drupal - Module for migration from vBulletin to Drupal. Advanced Forum - Module to build Drupal forums similar to systems like vBulletin. Choosing Drupal forum over vBulletin - Article on this option. To integrate a vBulletin forum within a Drupal website, see: Drupal vB - Drupal module for integration between vBulletin and Drupal. vbDrupal - Fork of Drupal to combine the powers of Drupal and vBulletin. vBulletin - Drupal group on vBulletin integration.

Search engine-friendly site migration When migrating a website from any system to Drupal you should be aware of existing inbound links to your site, as well as search engine indexes and ranking. In order to maintain your search engine ranking and also not break inbound links you should plan to redirect inbound requests to old uris to your new drupal nodes. Instead of offering up 404 Errors, you can direct users to the content they are looking for. In some cases using the path_redirect module may be sufficient. In other cases you may want to write redirect rules in your .htaccess file, and in still other cases, the method described below may work for you. Another step, which will help with search engine indexing is to install and configure xmlsitemap module and submit your sitemap to the major search engines for indexing. The rest of this article describes an approach that will parse he Search Engine query from the HTTP_REFERER and search the drupal website for what the user was actually looking for. Create a page node with PHP code enabled in the input format, and add the following code. <?php $searchengines = array( '^http://www\.google.*$' => 'q', '^http://www.googel.fi.*$' => 'q', '^http://.*search.msn.co.*results.*$' => 'q', '^http://.*\.mysearch.com/jsp/GGmain.jsp?searchfor=.*$' => 'searchfor', '^http://search.freeserve.com/.*$' => 'q', '^http://aolsearch.aol.co.*$' => 'query', '^http://search.yahoo.com.*$' => 'va', '^http://search.yahoo.com.*$' => 'p', '^http://www.bbc.co.uk/cgi-bin/search/.*' => 'q', '^http://www.tiscali.co.uk/search/results.php.*$' => 'query', '^http://www.altavista.com/web/results.*$' => 'q', '^http://search.hotbot.co.uk/cgi-bin/pursuit.*$' => 'query', '^http://www.excite.co.uk/search/web/results.*$' => 'q', '^http://uk.search.yahoo.com/search.*$' => 'p', '^http://search.wanadoo.*$' => 'q' ); $referer = getenv("HTTP_REFERER"); while( list( $regexp, $qsitem ) = each( $searchengines ) ) { if( eregi( $regexp, $referer ) ) { echo( t("<br/><h2>Search Engine Detected</h2>It would appear you arrived here on the wings of a search engine, so, I will search my local database and show you anything that matches what you were looking for:<br/>")); $url = parse_url( $referer ); $querystring = $url['query']; $querystring = explode( "&", $querystring ); while( list( , $value ) = each( $querystring ) )


{ $item = explode( "=", $value ); if( $item[0] == $qsitem ) { if( trim( $item[1] ) != '' ) { $item[1] = urldecode( $item[1] ); echo ( search_data( $item[1] ) ); } } } } } ?>

This provides a (partial) list of regular expressions for common search engines, with information as to which query string parameter is the query the user entered. The HTTP_REFERER value (the site the user clicked a link to get to your site) is then examined against this list. When a match is found, a search is done using the standard Drupal search call (search_data). This locates potential matches, and hopefully, keeps the user on your site. In order to use this, create a new node which allows PHP code. You can call it what you want, and put whatever explanatory text you like on it. You can set whatever path you like. Just drop the above code-clip into place. Then, in Administration -> Settings set the 404 handler to be the path to the new node you created, and voila, if the user arrives from a recognized search engine, their search is performed on your site. It's working nicely for me.

Troubleshooting a migration into a Drupal database: "page not found" or no titles listed At times, after you've imported your content into the appropriate Drupal tables, you get a "page not found" error or just various weirdnesses. Sometimes Drupal needs a kick in the pants to register your imported nodes. First, clear your caches. If that doesn't work, try clearing them again and running cron. Next, you may need some stronger medicine. Loading your nodes programmatically and saving them using Drupal's built in functions allows your nodes to register themselves with the various contributed modules on your site and can resolve various issues. I did this using a short script: Caution: if you have Pathauto enabled with it's default settings, this will create new aliases for all your nodes based on their titles. If you don't want this, either disable pathauto before running, or change pathauto's settings to not automatically alias all saved nodes if they already have a URL alias. <?php /**this script can be copied to a file in the root of your drupal install ** and invoked by visiting yoursite.com/name_of_file.php ** This loads and then programmically saves all the nodes ** in you drupal installation. ** You must change the 'chdir' directory to your Drupal root. **/ //change this to the root directory of your drupal installation: chdir('/home/members/quixote/sites/d6'); include_once('./includes/bootstrap.inc'); include_once('./modules/node/node.pages.inc'); drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); db_set_active('default'); $sql = "SELECT `nid` FROM node ORDER BY node.nid"; $results = db_query($sql); $old_dump = array(); while( $nodes = db_fetch_object($results) ) { print_r ($nodes->nid); $currentnode = node_load( $nodes->nid ); print_r($currentnode); node_save( $currentnode ); } ?>

Importing CSV files into Drupal The node import was not ready yet for D6 so I went with drupal 5. (though it will be ready shortly) I had a content type with 30 or more fields. These fields where a mix of CCK and Taxonomy - multi level.


The client handed me a large spread sheet and the first thing I did (after hours of doing it the wrong way) was made a quick mysql table to import it into. The client made Columns per Taxonomy and Sub for example 2 rows of content would look like. "eid","Taxonomic_Group", "Common_Name" 5,"Mammal | Carnivora | Canis | lupus familiaris","Dog" 6,"Mammal | Rodentia | Rattus | norvegicus","Brown Rat" Lost more fields but this is a good example eid was an external ID - CCK Taxonomic_Group was three columns which I reduced to one via a mysql concat command (you could do this in the spreadsheet but I am more comfortable with mysql) Here is the query I used. SELECT eid, CONCAT_WS(" | ", tax_group, tax_subgroup, genus, species) as tax, `common_name`, CONCAT_WS(" | ", bio_tissue, bio_subtissue) as bio, CONCAT_WS(" | ", antomical_structure, antomical_sub_structure) as ant, `age`, `sex`, `condition`, `note`, CONCAT_WS(" | ", `mat_type`, `mat_sub`, `mat_sub_sub`) `coord`, `author1`, `author2`, `author3`, `author4`, `author5`, `author6`, `author7`, `author8`, `author9`, `author10`, `author11`, `author12`, `author13`, `author14`, `author15`, `year`, `journal_title`, `journal`, `volume`, `start_page`, `end_page` FROM `biology_dept` Common Name was just the title of the node. Thing is node import would not import and create heirarchy taxonomy so I used Taxonomy CSV import To help clean things up when I messed up but did not want to do a full database recovery http://drupal.org/project/views_bulk_operations This creates and easy way to find and delete large amounts of nodes http://drupal.org/project/taxonomy_manager This can help move taxonomy around or delete a quick amount of items. Then of course mysqldump at the command line for quic backups Modules Used http://drupal.org/project/taxonomy_csv http://drupal.org/project/taxonomy_manager


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.