Bricolage Multi-Site Feature Technical Specification
This document outlines the technical details for implementing the multi-site feature described in MultiSite::FunctionalSpec. While that document explains what the feature will look like, this one explains how it will be implemented.
The API will undergo some pretty extensive changes in order to add the multi-site functionality to Bricolage. This section reviews the classes that will be changed, describing how they will be changed and how the underlying database will be altered.
Bric::Biz::Site will be added as a brand new class and first-class business
object. It will be implemented similarly to the other Bricolage business
objects, in that it will inherit from the Bric base class, will use
Bric::register_fields() to register its instance attributes and create
accessors, and will store its data in the database. It will of course have the
usual new(), lookup(), list(), list_ids(), and my_meths()
methods.
The attributes of the Bric::Biz::Site class will be as follows:
The attributes that can be used in the lookup() method to look up an
existing site in the database will be:
The attributes that can be used in the list() method and its friends to
search for sites in the database will be:
The Bric::Biz::Site data will be stored in the database in the ``site''
table. The columns will correspond to the attribute names. Of course the ``id''
will have a primary key index, and the ``name'' and ``domain_name'' columns will
each have a UNIQUE index.
A single default site, named ``Default'', will be added to the system. All existing objects in classes that relate to sites will be related to this one default site. Existing Bricolage installations that have found other ways to manage multiple sites will have to manually create new sites and migrate content from the one to the others as needed. Bricolage will make no guesses as to site structure in preexisting installations.
A new entry will be added to the ``class'' table in the database for the new
Bric::Biz::Site class. It's key_name will be ``site''.
A new default group, ``All Sites'', will be added, and all Bric::Biz::Site objects will be members of this group.
This new subclass of Bric::Util::Grp will manage groups of Bric::Biz::Site objects. It will require the creation of a new ``site_member'' table and ``seq_site_member'' sequence. Site groups will not be secret by default, and the Bric::Util::Grp::Site class will reference the new Bric::Util::Class object ID for the Bric::Biz::Site class.
The caching code in the Bric base class will be updated to allow looking up
objects in the cache by more than one parameter. This will allow classes that
support lookup by site_id plus a second parameter (usually name) to
continue to benefit from the cache.
The category class will get a new instance attribute, site_id. All
instances will be required to be associated with a site. The my_meths()
method will get new code to reflect this new attribute. The site_id
attribute will also be added to the list of attributes that can be used as
list() method search parameters. The lookup() method will be altered to
allow a category to be looked up by site_id and uri, rather than just
uri.
A new column, ``site__id'', will be added to the ``category'' table. It will have
a foreign key constraint linking it to the ``id'' column in the ``site'' table and
an index associated with it. The upgrade script will populate this column with
the ID of the single default site. Furthermore, the UNIQUE index on the
``uri'' column will be dropped and replaced with a non-UNIQUE index. A new
UNIQUE index will be added on the combination of the ``site__id'' and ``uri''
columns.
The output channel class will get the following new instance attributes:
The my_meths() method will get new code to reflect these new attributes,
and they will also be added to the list of attributes that can be used as
list() method search parameters. The lookup() method will be altered to
allow an output channel to be looked up by site_id and name, rather than
just name.
Two new columns, ``site__id'' and ``protocol'', will be added to the
``output_channel'' table. The ``site__id'' column will have a foreign key
constraint linking it to the ``id'' column in the ``site'' table and an index
associated with it. The upgrade script will populate this column with the ID
of the single default site. Furthermore, the UNIQUE index on the ``name''
column will be dropped and replaced with a new UNIQUE index on the
combination of the ``site__id'' and ``name'' columns.
The destination class will get a new instance attribute, site_id. All
instances will be required to be associated with a site. The my_meths()
method will get new code to reflect this new attribute. The site_id
attribute will also be added to the list of attributes that can be used as
list() method search parameters. Only output channels that are in the site
corresponding to the destination's site_id will be able to be associated
with the destination.
A new column, ``site__id'', will be added to the ``server_type'' table. It will
have a foreign key constraint linking it to the ``id'' column in the ``site''
table and an index associated with it. The upgrade script will populate this
column with the ID of the single default site. Furthermore, the UNIQUE
index on the ``name'' column will be dropped and replaced with a new UNIQUE
index on the combination of the ``site__id'' and ``name'' columns.
This subclass of Bric::Util::Coll will be used to manage collections of site
objects associated with element objects. The only method it will need to
impelement is save(), and it will be based on similar code in the other
Bric::Util::Coll subclasses.
The category class will get a new instance attribute, key_name. This
attribute will replace name as the required unique identifier for
elements. Existing installations will have this value set by the same code
that creates template names -- that is, all lower case, with spaces and
punctuation replaced with underscores.
The my_meths() method will get new code to reflect this new attribute, and
it will also be added to the list of attributes that can be used as list()
method search parameters. The lookup() method will be altered to allow an
element to be looked up by key_name instead of name.
Also, all top-level asset types will be required to be associated with one or
more site IDs. Thus Bric::Biz::AssetType will gain three new methods for
managing site associations: get_sites(), add_sites(), and
delete_sites(). These will be managed internally via a
Bric::Util::Coll::Site object.
Furthermore, the site associations will affect the output channels that can be
associated with an element object. Since an element can only be associated
with output channels that are in sites associated with the element, whenever a
site is removed from from association with an element, any output channels
from that site that are associated with the element must also be removed from
association with the element. Converseley, any output channel added to
association with the element (via the add_output_channel() method) must be
in a site associated with the element or an exception will be thrown.
The list() and friends methods will be modified to take site_id() as a
parameter, and will return a list of top-level elements associated with the
site ID passed.
A new NOT NULL column, ``key_name'', will be added to the ``element'' table.
The UNIQUE index on the ``name'' column will be dropped and replaced by a new
UNIQUE index on the ``key_name'' column.
A new table will be created, ``element__site'', to link elements with sites. It will contain the following columns:
id NUMERIC(10,0) NOT NULL
DEFAULT NEXTVAL('seq_element__site'),
element__id NUMERIC(10,0) NOT NULL,
site__id NUMERIC(10,0) NOT NULL,
primary_oc__id NUMERIC(10,0) NOT NULL
The ``element__id'' and ``site__id'' columns will have a UNIQUE index on them
to ensure that an element is associated with a site only once. Each will also
of course have a foreign key constraint to their relative tables. The
``primary_oc__id'' column will have a foreign key back to the output_channel
table. It will replace the column of the same name currently in the ``element''
table, and will identify the primary output channel for assets based on the
element and in the site.
The ``element__output_channel'' table will be modified to the extent that its ``element__id'' column will be replaced with an ``element__site__id'' column with a FK back to the new ``element__site'' table's ``id'' column. (Technically it should probably be renamed ``'', but since the change in table structure is minor, it may not be worth the effort of adjusting all the other relevant constraints.) All output channel associations must now be on a per-site-element association basis, rather than just a per-element basis, so thus the change.
All data will be migrated to be associated with the default site. The new ``element__site'' table will be populated with the IDs and primary output channel associations of the existing top-level elements. The ``primary_oc_id'' column in the ``element'' table will then have its associated constraints and indices dropped and will be renamed ``__primary_oc_id__old__''. Then the ``element__output_channel'' table will get a new ``element__site__id'' column, which will then be populated with the IDs of the appropriate values from the ``element__site'' table's ``id'' column. The ``element__id'' column will be renamed ``__element__id_old__'' and all of its associated Indices and constraints will be dropped.
The Bric::Biz::OutputChannel::Element class will be modified to handle the changes in the relationships between classes.
All objects in the the business asset element class will have their records
updated such that their name attributes will reflect the new key_name
attribute values of Bric::Biz::AssetType. The has_name() method will then
be altered to simply compare the name. In addition, the code in
Bric::Biz::Asset::Business::Parts::Tile::Data and in
Bric::Biz::Asset::Business::Parts::Tile::Container that sets the name
attribute of business asset elements will be moved into
Bric::Biz::Asset::Business::Parts::Tile and altered such that when a new
element is constructed, its name attribute will be populated from the
defining element's new key_name attribute instead of the old name
attribute.
The workflow class will get a new instance attribute, site_id. All
instances will be required to be associated with a site. The my_meths()
method will get new code to reflect this new attribute. The site_id
attribute will also be added to the list of attributes that can be used as
list() method search parameters. The lookup() method will be altered to
allow a workflow to be looked up by site_id and name, rather than just
name.
A new column, ``site__id'', will be added to the ``workflow'' table. It will have
a foreign key constraint linking it to the ``id'' column in the ``site'' table and
an index associated with it. The upgrade script will populate this column with
the ID of the single default site. Furthermore, the UNIQUE index on the
name column will be dropped and replaced with a new UNIQUE index will on
the combination of the ``site__id'' and ``name'' columns.
The asset class will get a new instance attribute, site_id. All instances
of story, media, and template assets will be required to be associated with a
site. The my_meths() method will get new code to reflect this new
attribute, and its value will also be added to the return values of
get_grp_ids() so that it can be included in any permission checks. Only
output channels and categories that are in the site corresponding to the
asset's site_id attribute will be able to be associated with the asset.
In addition to the changes inherited from Bric::Biz::Asset, the template class
will add the site_id attribute to the list of attributes that can be used as
list() method search parameters.
A new column, ``site__id'', will be added to the ``formatting'' table. It will have a foreign key constraint linking it to the ``id'' column in the ``site'' table and an index. The upgrade script will populate this column with the ID of the single default site.
In addition to the new site_id attribute inherited from Bric::Biz::Asset,
the business asset class will get another attribute, alias_id. This
attribute may optionally contain the ID for another business asset of the same
type (story or media), meaning that the business asset is an alias for the
business asset represented by the ID. Business assets with the alias_id
attribute set will not be able to associate an element (``tile'' in _init()),
and will not be able to change or remove the ID once it is set. Conversely,
business assets with an associated element will not be able to associate an
alias ID. That is to say, either an alias ID or an element will be required in
the new() constructor and will be permanent for the object thereafter.
Amen.
When a new alias story is created, the story to be aliased will be instantiated and its properties copied as much as possible. The element on which it is based will be required to be a part of the site with which the new alias story is created. The output channel settings will default to those set for the element in the current site. The category settings will attempt to match those set by the aliased story, but if they cannot, the story will be placed into the root category. [Hand-waving. I think that the code will have to do some experimentation to find a decent category with a unique URI.]
The attributes of the aliased story will be copied to the new story as much as possible, including title, description, slug, priority, and cover date. The publish date will not be set.
The get_tile() method will be updated to return the elements associated
with the aliased asset, if there is one. This will be used by templates, but
ignored by the story profile. Similarly, the get_keywords() and
get_contributors() methods will be altered to return the keywords and
contributors of the aliased story. [Note: Do we want to allow keywords to be
editable?]
Changes to Bric:Biz::Asset::Business::Story and Bric:Biz::Asset::Business::Media will be virtually identical.
The story and media classes will inherit the attribute changes of
Bric::Biz::Asset::Business. Of course, this means that site_id and
alias_id will be added to the list of attributes that can be used as
list() method search parameters. In addition, the check_uri method will
be updated to check for the uniqueness of stories within a site, since stories
in different sites can now share the same URIs.
New columns, ``site__id'' and ``alias_id'', will be added to the story and media
tables. The ``alias_id'' column will be NULLable and will have a foreign key
constraint and index to the table's own ``id'' column. It will also have a
constraint preventing it from referring to the ``id'' column in the same row. The
``site__id'' column will be NOT NULL and will have a foreign key constraint
and index to the ``id'' column in the ``site'' table.
The burner class will get a new method, best_uri() [alternate method name
suggestions welcome]. This method will attempt to simplify template
developers' lives by providing an interface for creating URLs for related
stories and media. It will look something like this:
sub best_uri {
my ($self, $ba, $protocol) = @_;
my ($site_id, $oc_id) = $self->_get(qw(site_id oc_id));
my $oc = $ba->get_output_channels($oc_id));
$protocol ||= $oc->get_protocol;
my $uri = '';
unless ($ba->get_site_id == $site_id) {
# The asset's not in this site. Try to lookup an alias in this site.
if (my $rel = $ba->lookup({ aliased_id => $ba->get_id,
site_id => $site_id })) {
# Use the alias, instead.
$ba = $rel;
} else {
# No alias. Construct a full URL.
$uri = $protocol . $ba->get_site->get_domain_name;
}
}
my $uri .= $ba->get_uri($oc, $protocol);
URI->new($uri);
}
New attributes for object IDs (as opposed to the current support for objects) will be added as necessary. Notice that the new method returns a URI object. This is to ensure good formatting of the URL. Template developers will then be able to display the URL like this (assuming Mason templating):
% my $relstory = $element->get_related_story or return; <a href="<% $burner->best_uri($relstory)->as_string %>" />
The HTML::Template burner class will be altered to make it easy for
HTML::Template templates to use the best_uri() method. This will be done by
adding a code reference to the functions parameter to
HTML::Template->new similar to the current page_link,
prev_page_link, and next_page_link functions. [Note: Sam, is this
necessary?] However, HTML::Template template developers may wish to just use
this method directly in the Perl templates:
my $media = $element->get_related_media; $template->param(link => $burner->best_uri($media)->as_string) if $media;
Changes to the user interface will consist entirely of additions to various profiles, the addition of a new administration manager, and some changes to the search managers and the workspace interface.
This development will include the creation of a new callback Mason component, comp/widgets/profile/site.mc, to handle the execution of callbacks from the site profile.
In addition, a new ``Key Name'' field will be added to the Element profile, and will be checked for uniqueness, rather than the ``Name'' field. Also, in the ``Custom Fields'' section, the ``Name'' field will be renamed ``Key Name'', and the ``Label'' field will either be renamed ``Name'' or will be consistently named Label`` (instead of switching names depending on the HTML field type selected).
The comp/widgets/formBuilder/element.mc callback Mason component will be updated to manage the site associations and field changes.
All stories will be associated with a site, and the site cannot be changed once a story is created. New stories will automatically be associated with the site that the current workflow is associated with. The story profile's ``Information'' section will display the name of the site the story is associated with.
A new checkbox will be added to the ``New Story'' profile, ``Make Alias''. If checked, when the ``Create'' button is clicked, instead of proceeding to the story profile, Bricolage will instead ignore any selected ``Story Type'' and proceed to display a story search interface. This interface will be identical to that currently used for the ``related story'' interface, and from it the user will select a single story. The selected story will become the ``aliased story'', and the new story will be the ``alias story''.
Next, Bricolage will examine the aliased story to see if it contains any related assets. If it does, any that the user has at least READ access to will be presented in a list, and each will have a checkbox next to it. The user can then select the checkbox for any assets that she also wants to be aliased, and then click a ``Continue'' button. If any related assets were selected to be aliases, they will be created and put on My Workspace.
This will admittedly be a tricky process. Here are the steps that I foresee will need to be taken in this process:
All of the above occurs in the new widget, comp/widgets/alias/alias.mc, and its associated callback component comp/widgets/alias/callback.mc. Once the user has selected any related assets she wants to alias, another callback will take the following steps to create the aliases:
Thereafter, any time the story profile for an alias story is accessed, the ``Content'' and ``Association'' sections will be read-only. Other sections will all be editable, and changes to them will not affect the values of the aliased story. [Note: Do we want to allow keywords to be editable?] The default values of the editable sections a new alias story will be filled in as described in the ``Bric::Biz::Asset'' section.
A special ``All Sites'' option will allow the user to see the workflows for all sites she's associated with; this option will be off by default, however, and can be enabled by setting a new bricolage.conf directive.
The ``Site Context'' list will only show up if the user has access to more than one site.
Changes to so many profiles will naturally require changes to at least a few of the managers, plus the addition of a new manager.
deactivate_cb callback.
The ``Advanced Find'' feature will be updated with a select list of sites to which the user has READ, EDIT, or CREATE permission, in order to limit the search to a particular site.
The ``Find Stories'' and ``Find Media'' managers will also have a new checkbox for each asset enabling the user to create a new alias asset. This will be similar to the checkbox for checking out one or more assets.
The comp/widgets/search callback files will be updated as necessary to handle these changes.
The descriptions of the changes to the profiles and managers above often include widget and callback changes. A few additional changes will need to be made to other widgets, as well.
key_name attribute.
The sideNav widget will also gain a new entry for the ``Sites'' manager under the ``System'' admin menu.
Since the structure of a number of objects is changing as part of this project, the SOAP interface will likely need to be updated to handle those changes.
In addition, the output channel associations will be limited to the output channels associated with the sites associated with the element. This means, of course, that site associations will have to occur first.
The element, output channel, and category associations will become dependent on the site association, as only those elements, output channels, and categories that are associated with the site will be available to be added or removed from association with the template. Naturally, this means that for new templates, the site association will occur first. Any attempts to add output channels or categories not associated with the site will trigger an exception.
First, a new element, ``site_id'' will be added. This value will only be setable when creating new stories. Attempts to update existing stories with a different ``site_id'' attribute value will trigger an exception. Code will also be added to validate that the user has EDIT permission to the assets in the site to which the story is being added.
The element, output channel, and category associations will become dependent on the site association, as only those elements, output channels, and categories that are associated with the site will be available to be added or removed from association with the story. Naturally, this means that for new stories, the site association will occur first. Any attempts to add output channels or categories not associated with the site will trigger an exception.
And finally, yet another new element, ``alias_id'', will be added. This value will only be setable when creating new stories. Attempts to update existing stories with a different ``alias_id'' attribute value or by associating an element will trigger an exception. Code will also be added to validate that the user has READ permission to the story represented by the ``alias_id''.
The ``element'' and ``alias_id'' attributes will be made mutually-exclusive. This means that the DTD will be updated to make them both optional, and the code will have to check for one or the other and throw an exception when both are included.
David Wheeler <david@kineticode.com>