eZ Platform enables you to maintain multiple sites in one installation using a feature called SiteAccesses.

In short, a SiteAccess is a set of configuration settings that is used when you reach the site through a specific address.
When the user visits the site, the system analyzes the URI and compares it to rules specified in the configuration. If it finds a set of fitting rules, this SiteAccess is used.

Settings defined per SiteAccess may include, among others, database, language or var directory.
When that SiteAccess is used, they override the default configuration.

different language versions of the same site identified by a uri part; one SiteAccess for one language

two different versions of a website: one SiteAccess with a public interface for visitors and one with a restricted interface for administrators

Enterprise

If you need to change between SiteAccesses in Page mode, do not use any functions in the page itself (for example, a language switcher). This may cause unexpected errors. Instead, switch between SiteAccesses using the SiteAccess bar above the page.

A site has content in two languages: English and Norwegian. It has one URI per language: http://example.com/eng and http://example.com/nor. Uri parts of each language (eng, nor) are mapped to a SiteAccess, commonly named like the URI part: eng, nor. Using semantic configuration, each of these SiteAccesses can be assigned a prioritized list of languages it should display:

The English site would display content in English and ignore Norwegian content;

The Norwegian site would display content in Norwegian but also in English if it does not exist in Norwegian.

Such configuration would look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

ezpublish:siteaccess:# There are two SiteAccesseslist:[eng,nor]# eng is the default one if no prefix is specifieddefault_siteaccess:eng# the first URI of the element is used to find a SiteAccess with a similar namematch:URIElement:1ezpublish:# root node for configuration per SiteAccesssystem:# Configuration for the 'eng' SiteAccesseng:languages:[eng-GB]nor:languages:[nor-NO,eng-GB]

Note

A new SiteAccess is recognized by the system, but an Anonymous User will not have read access to it until it is explicitly given via the Admin > Roles panel. Without read access the Anonymous User will simply be directed to the default login page.

Configuration is resolved depending on scope. It gives the opportunity to define settings for a given SiteAccess, for instance like in the legacy INI override system.

The available scopes are:

global

SiteAccess

SiteAccess group

default

The scopes are applied in the order presented. This means that global overrides all other scopes.
If global is not defined, the configuration will then try to match a SiteAccess, and then a SiteAccess group.
Finally, if no other scope is matched, default will be applied.

In short: if you want a match that will always apply, regardless of SiteAccesses use global.
To define a fallback, use default.

1
2
3
4
5
6
7
8
9
10
11
12
13
14

ezpublish:system:global:# If set, this value will be used regardless of any other var_dir configuration#var_dir: var/globalsite:# This var_dir will be used for the 'site' SiteAccessvar_dir:var/sitesite_group:# This will be overwritten by the SiteAccess above, since the SiteAccess has precedencevar_dir:var/groupdefault:# This value will only be used if there is no global, SiteAccess or SiteAccess group definedvar_dir:var/site

Note that you should avoid defining a setting twice within the same scope, as this will cause a silent failure.

This mechanism is not limited to eZ Platform internal settings (the ezsettings namespace) and is applicable for specific needs (bundle-related, project-related, etc.).

ezpublish.siteaccess.default_siteaccess is the default SiteAccess that will be used if matching was not successful. This ensures that a SiteAccess is always defined.

ezpublish.siteaccess.list is the list of all available SiteAccesses in your website.

ezpublish.siteaccess.groups(optional) defines which groups SiteAccesses belong to. This is useful when you want to mutualize settings between several SiteAccesses and avoid config duplication. Siteaccess groups are treated the same as regular SiteAccesses as far as configuration is concerned. A SiteAccess can be part of several groups. A SiteAccess configuration has always precedence on the group configuration.

ezpublish.siteaccess.match holds the matching configuration. It consists in a hash where the key is the name of the matcher class. If the matcher class doesn't start with a \ , it will be considered relative to eZ\Publish\MVC\SiteAccess\Matcher (e.g. Map\Host will refer to eZ\Publish\MVC\SiteAccess\Matcher\Map\Host)

Every custom matcher can be specified with a fully qualified class name (e.g. \My\SiteAccess\Matcher) or by a service identifier prefixed by @ (e.g. @my_matcher_service).

In the case of a fully qualified class name, the matching configuration will be passed in the constructor.

In the case of a service, it must implement eZ\Bundle\EzPublishCoreBundle\SiteAccess\Matcher. The matching configuration will be passed to setMatchingConfiguration().

Note

Make sure to type the matcher in correct case. If it is in wrong case like "Uri" instead of "URI," it will work well on systems like Mac OS X because of their case-insensitive file system, but will fail when you deploy it to a Linux server. This is a known artifact of PSR-0 autoloading of PHP classes.

They are based on logical combinations, or/and, using logical compound matchers:

Compound\LogicalAnd

Compound\LogicalOr

Each compound matcher will specify two or more sub-matchers. A rule will match if all the matchers combined with the logical matcher are positive. The example above would have used Map\Host and Map\Uri, combined with a LogicalAnd. When both the URI and host match, the SiteAccess configured with "match" is used.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# ezplatform.ymlezpublish:siteaccess:match:Compound\LogicalAnd:# Nested matchers, with their configuration.# No need to specify their matching values (true is enough).site_en:matchers:Map\URI:en:trueMap\Host:example.com:truematch:site_ensite_fr:matchers:Map\URI:fr:trueMap\Host:example.com:truematch:site_frMap\Host:admin.example.com:site_admin

In some cases, after matching a SiteAccess, it is necessary to modify the original request URI. This is for example needed with URI-based matchers since the SiteAccess is contained in the original URI and is not part of the route itself.

The problem is addressed by analyzing this URI and by modifying it when needed through the URILexer interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

// URILexer interface/** * Interface for SiteAccess matchers that need to alter the URI after matching. * This is useful when you have the SiteAccess in the URI like "/<siteaccessName>/my/awesome/uri" */interface URILexer{ /** * Analyses $uri and removes the SiteAccess part, if needed. * * @param string $uri The original URI * @return string The modified URI */ public function analyseURI( $uri ); /** * Analyses $linkUri when generating a link to a route, in order to have the SiteAccess part back in the URI. * * @param string $linkUri * @return string The modified link URI */ public function analyseLink( $linkUri );}

Once modified, the URI is stored in the semanticPathinfo request attribute, and the original pathinfo is not modified.

When using the multisite feature, it is sometimes useful to be able to generate cross-links between different sites within one installation.
This allows you to link different resources referenced in the same content repository, but configured independently with different tree roots.

To implement this feature, a new VersatileMatcher was added to allow SiteAccess matchers to be able to reverse-match.
All existing matchers implement this new interface, except the regexp-based matchers which have been deprecated.

The SiteAccess router has been added a matchByName() method to reflect this addition. Abstract URLGenerator and DefaultRouter have been updated as well.

Note

SiteAccess router public methods have also been extracted to a new interface, SiteAccessRouterInterface.

There are two known limitations to moving between SiteAccesses in eZ Enterprise's Landing Pages:

On a Landing Page you can encounter a 404 error when clicking a relative link which points to a different SiteAccess (if the Content item being previewed does not exist in the previously used SiteAccess). This is because detecting SiteAccesses when navigating in preview is not functional yet. This is a known limitation that is awaiting resolution.

When navigating between SiteAccesses in the back office using the top bar, you are always redirected to the main page, not to the Content item you started from.

Dynamic configuration is handled by a config resolver. It consists in a service object mainly exposing hasParameter() and getParameter() methods. The idea is to check the different scopes available for a given namespace to find the appropriate parameter.

In order to work with the config resolver, your dynamic settings must comply internally with the following name format: <namespace>.<scope>.parameter.name.

The following configuration is an example of internal usage inside the code of eZ Platform:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Namespace + scope exampleparameters:# Some internal configurationezsettings.default.content.default_ttl:60ezsettings.ezdemo_site.content.default_ttl:3600# Here "myapp" is the namespace, followed by the SiteAccess name as the parameter scope# Parameter "foo" will have a different value in ezdemo_site and ezdemo_site_adminmyapp.ezdemo_site.foo:barmyapp.ezdemo_site_admin.foo:another value# Defining a default value, for other SiteAccessesmyapp.default.foo:Default value# Defining a global setting, used for all SiteAccesses#myapp.global.some.setting:This is a global value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

// Inside a controller, assuming SiteAccess being "ezdemo_site"/** @var $configResolver \eZ\Publish\Core\MVC\ConfigResolverInterface **/$configResolver = $this->getConfigResolver();// ezsettings is the default namespace, so no need to specify it// The following will resolve ezsettings.<siteaccessName>.content.default_ttl// In the case of ezdemo_site, will return 3600.// Otherwise it will return the value for ezsettings.default.content.default_ttl (60)$locationViewSetting = $configResolver->getParameter( 'content.default_ttl' );$fooSetting = $configResolver->getParameter( 'foo', 'myapp' );// $fooSetting's value will be 'bar'// Force scope$fooSettingAdmin = $configResolver->getParameter( 'foo', 'myapp', 'ezdemo_site_admin' );// $fooSetting's value will be 'another value'// Note that the same applies for hasParameter()

Both getParameter() and hasParameter() can take 3 different arguments:

$paramName (the name of the parameter you need)

$namespace (your application namespace, myapp in the previous example. If null, the default namespace will be used, which is ezsettings by default)

$scope (a SiteAccess name. If null, the current SiteAccess will be used)

The DynamicSettingParser service that can be used for adding support of the dynamic settings syntax.
This service has ezpublish.config.dynamic_setting.parser for ID and implementseZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\SiteAccessAware\DynamicSettingParserInterface.

It is not possible to use dynamic settings in your semantic configuration (e.g. config.yml or ezplatform.yml) as they are meant primarily for parameter injection in services.

It is not possible to define an array of options having dynamic settings. They will not be parsed. Workaround is to use separate arguments/setters.

Injecting dynamic settings in request listeners is not recommended, as it won't be resolved with the correct scope (request listeners are instantiated before SiteAccess match). Workaround is to inject the ConfigResolver instead, and resolve the setting in your onKernelRequest method (or equivalent).

Setter injection for dynamic settings should always be preferred, as it makes it possible to update your services that depend on them when ConfigResolver is updating its scope (e.g. when previewing content in a given SiteAccess). However, only one dynamic setting should be injected by setter.

parameters:acme_test.my_service.class:Acme\TestBundle\MyServiceClass# "acme" is our parameter namespace.# Null is the default value.acme.default.some_parameter:~acme.ezdemo_site.some_parameter:fooacme.ezdemo_site_admin.some_parameter:barservices:acme_test.my_service:class:%acme_test.my_service.class%# The following argument will automatically resolve to the right value, depending on the current SiteAccess.# We specify "acme" as the namespace we want to use for parameter resolving.calls:-[setSomeParameter,["$some_parameter;acme$"]]