Creating a Site with Symphony

I ran into a bug using Symphony. I don’t know enough about PHP and the Symphony core to be able to monkey around with it to discover what exactly is going on to produce the behaviour I am finding. I’m using Textile to format the entries for a client site I developed with Symphony 1.5. The site has since been updated to Symphony 1.7, but now when I resave data sources, the text formatter is disabled and the XML data for text area fields is being returned without formatting. Without child elements to parse, the XSL templates do not output the text when using instructions such as xsl:copy-of select="body/*".

Considering the Options

So, how should I go about fixing the problem?

  • Learn PHP, figure out how Symphony works, and fix the problem
  • Use a manual hack every time I save a data source
  • Rebuild from scratch

Considering the options, I think I could probably rebuild the site faster than I could learn how to fix the bug. The hack doesn’t seem like a good long term solution. If the source of the problem is a migration from an earlier version gone wrong, I might as well take this opportunity to rebuild the site and optimize the code, based on what I’ve learned since I first built the site. There are some things that I would do differently now.

Download Symphony

Symphony is a web publishing system developed by Twentyone Degrees in Brisbane, Australia. The system is powered by XML and XSLT, standards recommended by the W3C. Symphony 1.7 is free, but not open source. Symphony 2 Beta Revision 5 is the first open source release available from Twentyone Degrees. To download either version will require registering at Symphony Accounts. Once you have logged in, click on the Download Symphony link to download Symphony 1.7.01.

Install Symphony

I will be developing the site on my local development server. I am using the Apache web server that comes installed on Mac OS X. I have added support for PHP 5 and MySQL 5. PHP is configured to process XSLT. Apache is configured to allow .htaccess files to override the default configuration settings and use mod_rewrite rules.

Create Virtual Hosts

I have created a couple virtual hosts by modifying my /etc/hosts file by adding the following:

127.0.0.1       sitename.dev
127.0.0.1       sitename.local

Then I modify the virtual hosts configuration file for Apache. For Mac OS X versions previous to 10.5 Leopard, I would add the code to the end of the Apache configuration file found here: /etc/httpd/httpd.conf. Now that Leopard is using Apache 2, the location for the virtual hosts configuration file is here: /etc/apache2/extra/httpd-vhosts.conf. I have my virtual hosts organized in my user Sites directory. I’ll set the location of these virtual hosts with something like this:

<VirtualHost 127.0.0.1>
    ServerName sitename.dev
    DocumentRoot /Users/Stephen/Sites/domains/sitename/dev/www
</VirtualHost>

<VirtualHost 127.0.0.1>
    ServerName sitename.local
    DocumentRoot /Users/Stephen/Sites/domains/sitename/local/www
</VirtualHost>

Then, gracefully restart Apache, so the web server configuration can be updated:

sudo apachectl graceful

So, now I have two locations available for comparing the existing site and the development site. I have the existing site, in its backup state before things started to go awry, installed in the sitename.local location. The development site is in the sitename.dev location.

I’m going to start without a workspace. This allows you to start with nothing but an empty database and an empty workspace. Otherwise, the default theme will be installed with example entries. I like to start clean.

Clean Install

For a clean install of Symphony, all you need are the following files/directories:

  • index.php
  • install.php
  • symphony

I have copied these files into the development directory, so the directory structure looks like this:

/Users/Stephen/Sites/domains/sitename/dev/www/index.php
/Users/Stephen/Sites/domains/sitename/dev/www/install.php
/Users/Stephen/Sites/domains/sitename/dev/www/symphony

File Permissions

Before installing, the directory and file permissions need to be set to ensure that installation is trouble free. This can be accomplished by running the following commands in Terminal. First, change directories:

cd ~/Sites/domains/sitename/dev

Then, change the permissions to allow the installer to write files to these directories.

chmod -R 755 www
chmod 777 www
chmod 777 www/symphony

Create a MySQL Database

Symphony requires a connection to a MySQL database, so I’ll create this first before proceeding with the install. I use phpMyAdmin to create the database.

create database sitename_dev

Installation

Navigate to the install file and follow the instructions to provide the database and user details:

http://sitename.dev/install.php
Environment Settings
  • Root Path: /Users/Stephen/Sites/domains/sitename/dev/www
Database Connection
  • Database: sitename_dev
  • Username: root
  • Password: ****
  • Host: localhost
  • Port: 3306
  • Table Prefix: sym_
Permission Settings
  • Files: 0777
  • Directories: 0755
User Information
  • Username
  • Password
Personal Information
  • First Name
  • Last Name
  • Email Address

The install takes a single click, and I am now looking at the Symphony login window, as all has gone well. This is where I will be logging into the Symphony administration interface.

http://sitename.dev/symphony

I like to be able modify XSL templates and configuration files directly from the file system, using TextWrangler or Coda as my editors of choice. If I list the files in Terminal to determine the permissions, I see that the files have been created as the default Apache user, _www.

cd ~/Sites/domains/sitename/dev/www
ls -la

drwxr-xr-x  12 Stephen  Stephen    408 Jul 19 14:45 .
drwxr-xr-x   4 Stephen  Stephen    136 Jul 19 07:20 ..
-rwxr-xr-x@  1 Stephen  Stephen   6148 Jul 19 14:22 .DS_Store
-rwxrwxrwx   1 _www     Stephen   1883 Jul 19 12:50 .htaccess
-rwxr-xr-x@  1 Stephen  Stephen   1050 Apr  1  2007 README
drwxr-xr-x   2 _www     Stephen     68 Jul 19 12:50 campfire
-rwxr-xr-x@  1 Stephen  Stephen   6460 Mar 16  2007 index.php
-rw-r--r--   1 _www     Stephen   2053 Jul 19 12:50 install-log.txt
-rw-r--r--@  1 Stephen  Stephen  88956 Mar 27  2007 install.php
drwxr-xr-x   6 _www     Stephen    204 Jul 19 12:50 manifest
drwxr-xr-x@ 12 Stephen  Stephen    408 Mar 22  2007 symphony
drwxr-xr-x   9 _www     Stephen    306 Jul 19 12:50 workspace

This list clearly shows the files and directories that Symphony has created during the install process. To make sure I can still access these files from the file system without much fuss, I’m going to change the permissions on these files, recursively changing the owner and group to my username:

cd ~/Sites/domains/sitename/dev
sudo chown -R Stephen:Stephen www

The first thing I would want to do at this point is to delete the install.php file for security reasons, especially if I was working on a production server.

Hello World

Symphony is installed, so I’ll start going through the process of building the home page. For anyone unfamiliar with Symphony, I have created an introduction to Building a Symphony Theme. Unfortunately, I haven’t had a chance to finish the tutorial. I hope I can remedy this by completing the documentation for developing this site, assuming that everyone knows the basics as detailed in these articles:

Install Campfire Services

This is an important step, since the preference set for each Author determines the text formatter to be used for all entries created by that Author. Text formatters are generally added to Symphony as Campfire Services. I will be using the Textile text formatter. Check out the available Campfire Services on the Overture Forum.

Plan the Site Structure

The site requires several types of content: static content pages, news and archives, images, projects organized by country and project partner, a repository of resources, a means of registering members who are provided with access to resources by logging into a members area. Future requirements include photo and video galleries. With that in mind, the site will be built with the following sections:

Sections

  • Content
  • News
  • Resources
  • Categories
  • Campaigns
  • Projects
  • Countries
  • Partners
  • Members

With a site structure in mind, I can develop a custom administration interface by populating each section with custom fields to manage all the types of data that will be used by each section. I have a choice of several different field types: Text Input, Text Area, Select Box, Check Box, File Attachment and Section Link (one of my contributions to the development of Symphony).

Custom Fields

  • Content
    • Title (Text Input)
    • Page (Select Box)
    • Body (Text Area)
    • Photo (File Attachment)
    • Photo Title (Text Input)
    • Publish (Check Box)
  • News
    • Title (Text Input)
    • Category (Section Link: Categories)
    • Description (Text Area)
    • Body (Text Area)
    • Photo (File Attachment)
    • Photo Title (Text Input)
    • Publish (Check Box)
  • Resources
    • Resource (Text Input)
    • File (File Attachment)
    • Category (Section Link: Categories)
    • Description (Text Area)
  • Categories
    • Title (Text Input)
    • Description (Text Area)
  • Campaigns
    • Campaign Year (Text Input)
    • Description (Text Area)
    • Partners (Section Link: Multiple)
    • Countries (Section Link: Multiple)
  • Projects
    • Project (Text Input)
    • Project Year (Select Box)
    • Project Country (Section Link: Countries)
    • Project Partner (Section Link: Partners)
    • Description (Text Area)
    • Details (Text Area)
    • Photo (File Attachment)
    • Photo Title [Portrait Orientation] (Checkbox)
    • Sort (Text Input)
  • Countries
    • Country (Text Input)
    • Continent (Select Box)
    • Country Code (Text Input)
    • Description (Text Area)
    • Government (Text Input)
  • Partners
    • Partner (Text Input)
    • Full Name (Text Input)
    • Type (Select Box)
    • Description (Text Area)
    • Website Title (Text Input)
    • Website URL (Text Input)
    • Logo (File Attachment)
  • Members
    • Username (Text Input)
    • First Name (Text Input)
    • Last Name (Text Input)
    • Email (Text Input)
    • Phone (Text Input)
    • Address 1 (Text Input)
    • Address 2 (Text Input)
    • City (Text Input)
    • Province / State (Text Input)
    • Country (Text Input)
    • Postal / Zip Code (Text Input)
    • Role (Select Box)
    • Role Other (Text Input)
    • Communication Method (Select Box)
    • Organizing Class (Text Input)
    • Organization Name (Text Input)
    • Organization Type (Select Box)
    • Organization Type Other (Text Input)
    • Organization Address 1 (Text Input)
    • Organization Address 2 (Text Input)
    • Organization City (Text Input)
    • Organization Province / State (Text Input)
    • Organization Country (Text Input)
    • Organization Postal / Zip Code (Text Input)
    • Organization Email (Text Input)
    • Organization Phone (Text Input)
    • Organization Facsimile (Text Input)

Create the Master Template

Create a new master template [Blueprints : Components : Create New] called “default”.

Then create a page [Blueprints : Pages : Create New] called “Home”, configuring the page type as “index”, that uses the default.xsl master template.

The default template will look something like this:

  <?xml version="1.0" encoding="UTF-8" ?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  
  <xsl:output method="xml"
    doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
    doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
    encoding="UTF-8"
    indent="yes" />
  
  <xsl:param name="campaign-year" select="2008"/>
  <xsl:param name="logged-in" select="/data/events/user/@logged-in"/>
  
  <xsl:template match="/">
    <html>
      <head>
        <title><xsl:value-of select="$website-name"/> - <xsl:value-of select="$page-title"/></title>
        <link rel="stylesheet" type="text/css" media="screen" href="{$root}/workspace/assets/css/hri.css"/>
        <link rel="alternate" type="application/rss+xml" href="/rss/" />
      </head>
      <body id="section-{$current-page}">
        <div id="wrapper">
          <ul id="global-nav">
            <xsl:call-template name="global-nav"/>
          </ul>
          <div id="header">
            <h1><a href="{$root}" id="hri-link"><span><xsl:value-of select="$website-name"/></span></a></h1>
            <div id="main-nav">
              <ul>
              <xsl:call-template name="navigation"/>
              </ul>
            </div>
          </div>
          <div id="body-container">
            <xsl:apply-templates />
            <div class="clear"></div>
          </div>
          <div id="footer">
            <div id="copyright">
              <p>Copyright 2008 Hunger Response International. All rights reserved. <a href="{$root}/privacy/" title="Hunger Response International / Rice Raiser Privacy Policy">Privacy Policy</a>
              | Site Design by <a href="http://www.bauhouse.ca/" title="Bauhouse Visual Communications">Bauhouse</a></p>
            </div>
          </div>
        </div>
      </body>
    </html>   
  </xsl:template>
  
  </xsl:stylesheet>

Create the Navigation Utility

This template makes reference to a couple templates that do not yet exist. I can add these named templates after the match template. Or if I want this template to be accessible to all master templates, I can create a utility without attaching it to a specific data source.

  <xsl:template name="global-nav">
    <xsl:for-each select="/data/navigation/page[@type = 'global' or @type = 'index']">
      <xsl:choose>
        <xsl:when test="@type = 'index'">
          <li>
            <xsl:if test="@handle = $current-page">
              <xsl:attribute name="class">current</xsl:attribute>
            </xsl:if>
            <a href="{$root}/"><xsl:value-of select="title"/></a>
          </li>
        </xsl:when>
        <xsl:otherwise>
          <li>
            <xsl:if test="@handle = $current-page">
              <xsl:attribute name="class">current</xsl:attribute>
            </xsl:if>
            <a href="{$root}/{@handle}/"><xsl:value-of select="title"/></a>
          </li>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>
  
  <xsl:template name="main-nav">
    <xsl:choose> 
      <xsl:when test="$logged-in='true'">
        <xsl:for-each select="/data/navigation/page[@type='members']">
          <li>
            <xsl:if test="@handle = $current-page">
              <xsl:attribute name="class">current</xsl:attribute>
            </xsl:if>
            <a href="{$root}/{@handle}/"><xsl:value-of select="title"/></a>
          </li>
        </xsl:for-each>
        <li><a>Logged in as <xsl:value-of select="/data/events/user/username"/></a></li>
        <li><a href="{$root}/symphony/?page=/logout/">Logout</a></li>
      </xsl:when>
      <xsl:otherwise>
        <xsl:for-each select="/data/navigation/page[@type='default' or @type='login']">
          <li>
            <xsl:if test="@handle = $current-page">
              <xsl:attribute name="class">current</xsl:attribute>
            </xsl:if>
            <a href="{$root}/{@handle}/"><xsl:value-of select="title"/></a>
          </li>
        </xsl:for-each>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

Create the Page Structure

With the navigation in place, now we can start building the page structure. The pages have been created with the following parameters: Title (Master, URL Schema, Page Type)

  • Home (default.xsl, entry, index)
  • Partners (index.xsl, entry, global)
  • Projects (index.xsl, year/entry, global)
  • News (default.xsl, year/month/day/entry, global)
  • Donate (default.xsl, entry, global)
  • About (default.xsl, entry, global)
    • Board of Directors (default.xsl, entry, about)
    • Advisory Council (default.xsl, entry, about)
  • Contact (default.xsl, entry, global)
  • Resources (default.xsl, category/entry, members)
  • Register (default.xsl, n/a, Default)
  • Login (default.xsl, entry, login)
  • Privacy (default.xsl, entry, legal)
  • Maintenance (default.xsl, n/a, Maintenance)
  • Page Not Found (default.xsl, n/a, Page Not Found)

Creating Templates for Common Elements

I’m going to make sure that modifying the templates will be necessary only in a single place. I’ll create a template for common elements such as the head element, navigation and footer. The default master template now looks like this:

  <?xml version="1.0" encoding="UTF-8" ?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  
    <xsl:output method="xml"
      doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
      doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
      encoding="UTF-8"
      indent="yes" />
    
    <xsl:param name="campaign-year" select="2008"/>
    <xsl:param name="logged-in" select="/data/events/user/@logged-in"/>
    
    <xsl:template match="/">
      <html>
        <xsl:call-template name="head"/>
        <body id="section-{$current-page}">
          <div id="wrapper">
            <xsl:call-template name="navigation"/>
            <div id="body-container">
              <xsl:apply-templates />
              <div class="clear"></div>
            </div>
            <xsl:call-template name="footer"/>
          </div>
        </body>
      </html>  
    </xsl:template>
  </xsl:stylesheet>

The common elements are being called from a utility called “Common” that contains three named templates:

  <xsl:template name="head">
    <head>
      <title><xsl:value-of select="$website-name"/> - <xsl:value-of select="$page-title"/></title>
      <link rel="stylesheet" type="text/css" media="screen" href="{$root}/workspace/assets/css/hri.css"/>
      <link rel="alternate" type="application/rss+xml" href="/rss/" />
    </head>
  </xsl:template>
  
  <xsl:template name="navigation">
    <ul id="global-nav">
      <xsl:call-template name="global-nav"/>
    </ul>
    <div id="header">
      <h1><a href="{$root}" id="hri-link"><span><xsl:value-of select="$website-name"/></span></a></h1>
      <div id="main-nav">
        <ul>
        <xsl:call-template name="main-nav"/>
        </ul>
      </div>
    </div>
  </xsl:template>
  
  <xsl:template name="footer">
    <div id="footer">
      <div id="copyright">
        <p>Copyright 2008 Hunger Response International. All rights reserved. <a href="{$root}/privacy/" title="Hunger Response International / Rice Raiser Privacy Policy">Privacy Policy</a>
        | Site Design by <a href="http://www.bauhouse.ca/" title="Bauhouse Visual Communications">Bauhouse</a></p>
      </div>
    </div>
  </xsl:template>

Content and Navigation Utilities

I’ve created some templates to help automate the process of building content and section menus for pages. For page content, I have created a “Page Content” utility.

Page Content

  <xsl:template name="page-body">
    <xsl:param name="page-datasource" select="content/entry"/>
    <div id="section">
      <div class="section-head">
        <h2>
          <a href="{$root}/{$current-page}/">
            <xsl:value-of select="$page-title"/>
          </a>
        </h2>
      </div>
      <div class="section-menu">
        <ul>
          <xsl:call-template name="section-menu">
            <xsl:with-param name="page-datasource" select="$page-datasource"/>
          </xsl:call-template>
        </ul>
      </div>
    </div>
    <div id="main-content">
      <xsl:call-template name="page-content">
        <xsl:with-param name="page-datasource" select="$page-datasource" />
      </xsl:call-template>
    </div>
  </xsl:template>
  
  <xsl:template name="page-content">
    <xsl:param name="page-datasource" select="content/entry"/>
    <xsl:choose>
      <xsl:when test="($entry)">
        <xsl:for-each select="$page-datasource[@handle=$entry]">
          <div class="content-head">
            <h2><a href="{$root}/{$current-page}/{@handle}/" title="Permanent link to this article"><xsl:value-of select="fields/title"/></a></h2>
          </div>
          <div class="content-body">
            <xsl:if test="(fields/photo/item)">
              <p class="photo"><img src="{$root}/{fields/photo/item/path}" title="{fields/photo-title}"/></p>
            </xsl:if>
            <xsl:apply-templates select="fields/body/*" mode="root-url"/>
          </div>
        </xsl:for-each>
      </xsl:when>
      <xsl:otherwise>
        <xsl:for-each select="$page-datasource[fields/page/@handle=$current-page][1]">
          <div class="content-head">
            <h2><a href="{$root}/{$current-page}/{@handle}/" title="Permanent link to this article"><xsl:value-of select="fields/title"/></a></h2>
          </div>
          <div class="content-body">
            <xsl:apply-templates select="fields/body/*" mode="root-url"/>
          </div>
        </xsl:for-each>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

Page Navigation

The “Page Content” utility calls templates from the “Page Navigation” utility:

  <xsl:template name="section-menu">
    <xsl:param name="page-datasource" select="/data/content-titles/entry"/>
    <xsl:param name="for-section" select="$current-page"/>
    <xsl:for-each select="$page-datasource[fields/page/@handle=$for-section]">
      <li>
        <xsl:if test="(not($entry) and position() = 1 and $current-page = $for-section) or @handle = $entry">
          <xsl:attribute name="class">current</xsl:attribute>
        </xsl:if>
        <a href="{$root}/{$for-section}/{@handle}/"><xsl:value-of select="fields/title"/></a>
      </li>
    </xsl:for-each>
  </xsl:template>
  
  <xsl:template name="projects-menu">
    <xsl:param name="page-datasource" select="/data/projects/entry"/>
    <xsl:param name="for-year" select="2007"/>
    <xsl:for-each select="$page-datasource[fields/project-year=$for-year]">
      <xsl:sort select="fields/project-number"/>
      <li>
        <xsl:if test="($year and not($entry) and position() = 1) or @handle = $entry">
          <xsl:attribute name="class">current</xsl:attribute>
        </xsl:if>
        <a href="{$root}/projects/{$for-year}/{@handle}/"><xsl:value-of select="fields/project"/></a>
      </li>
    </xsl:for-each>
  </xsl:template>
  
  <xsl:template name="partners-menu">
    <xsl:param name="global" select="'no'"/>
    <xsl:for-each select="partners/entry[fields/global=$global]">
      <li>
        <xsl:if test="@handle = $entry">
          <xsl:attribute name="class">current</xsl:attribute>
        </xsl:if>
        <a href="{$root}/partners/{@handle}/"><xsl:value-of select="fields/full-name"/></a>
      </li>
    </xsl:for-each>
  </xsl:template>

Root URL Utility

The xsl:apply-templates instruction used by the page-content template is referring a set of templates that I use to define a mode called root-url. This allows me to publish the Symphony site within a subdirectory while automatically modifying absolute href attributes that have been created as entry content to point to the href relative to the current location using Symphony’s $root parameter.

  <xsl:template match="*" mode="root-url">
    <xsl:element name="{name(.)}">
      <xsl:apply-templates select="@*" mode="root-url"/>
      <xsl:apply-templates mode="root-url"/>
    </xsl:element>
  </xsl:template>
  
  <xsl:template match="@*" mode="root-url">
    <xsl:attribute name="{name(.)}"><xsl:value-of select="."/></xsl:attribute>
  </xsl:template>
  
  <xsl:template match="text()" mode="root-url">
    <xsl:value-of select="."/>
  </xsl:template>
  
  <xsl:template match="img[substring(@src,1,1)='/']" mode="root-url">
    <img>
      <xsl:apply-templates select="@*" mode="root-url"/>
      <xsl:attribute name="src"><xsl:value-of select="$root"/><xsl:value-of select="@src"/></xsl:attribute>
    </img>
  </xsl:template>
  
  <xsl:template match="a[substring(@href,1,1)='/']" mode="root-url">
    <a>
      <xsl:apply-templates select="@*" mode="root-url"/>
      <xsl:attribute name="href"><xsl:value-of select="$root"/><xsl:value-of select="@href"/></xsl:attribute>
      <xsl:value-of select="."/>
      <xsl:apply-templates select="./*" mode="root-url"/>
    </a>
  </xsl:template>

Utilities

So far, I have the following list of utilities that are used by all pages, since they have not been attached to a specific data source:

  • Common (common.xsl)
  • Navigation (navigation.xsl)
  • Page Content (page-content.xsl)
  • Page Navigation (page-navigation.xsl)
  • Root URL (root-url.xsl)
Advertisements

About this entry