Index: wcs/admin/settings.ptl
===================================================================
--- wcs/admin/settings.ptl	(révision 2232)
+++ wcs/admin/settings.ptl	(copie de travail)
@@ -289,7 +289,7 @@
 
         '<dl>'
         '<dt><a href="sitename">%s</a></dt> <dd>%s</dd>' % (
-                _('Site Name'), _('Configure site name'))
+                _('Site Name and Addresses'), _('Configure site name and addresses'))
         '<dt><a href="language">%s</a></dt> <dd>%s</dd>' % (
                 _('Language'), _('Configure site language'))
         '<dt><a href="themes">%s</a></dt> <dd>%s</dd>' % (
@@ -646,6 +646,12 @@
         misc_cfg = get_cfg('misc', {})
         form.add(StringWidget, 'sitename', title = _('Site Name'),
                 value = misc_cfg.get('sitename', ''))
+        form.add(StringWidget, 'frontoffice-url', size=32,
+                title = _('Frontoffice base URL'),
+                value = misc_cfg.get('frontoffice-url', ''))
+        form.add(StringWidget, 'backoffice-url', size=32,
+                title = _('Backoffice base URL'),
+                value = misc_cfg.get('backoffice-url', ''))
 
         form.add_submit('submit', _('Submit'))
         form.add_submit('cancel', _('Cancel'))
@@ -653,12 +659,12 @@
             return redirect('.')
 
         if not form.is_submitted() or form.has_errors():
-            get_response().breadcrumb.append(('sitename', _('Site Name')))
-            html_top('settings', title = _('Site Name'))
-            '<h2>%s</h2>' % _('Site Name')
+            get_response().breadcrumb.append(('sitename', _('Site Name and Addresses')))
+            html_top('settings', title = _('Site Name and Addresses'))
+            '<h2>%s</h2>' % _('Site Name and Addresses')
             form.render()
         else:
-            cfg_submit(form, 'misc', ['sitename'])
+            cfg_submit(form, 'misc', ['sitename', 'frontoffice-url', 'backoffice-url'])
             redirect('.')
 
     def sms [html] (self):
Index: wcs/qommon/publisher.py
===================================================================
--- wcs/qommon/publisher.py	(révision 2235)
+++ wcs/qommon/publisher.py	(copie de travail)
@@ -61,6 +61,7 @@
 import logger
 import storage
 import strftime
+import urllib
 
 class ImmediateRedirectException(Exception):
     def __init__(self, location):
@@ -97,7 +98,10 @@
     app_translations = dict()
 
     def get_root_url(self):
-        return self.get_request().environ['SCRIPT_NAME'] + '/'
+        if self.get_request():
+            return self.get_request().environ['SCRIPT_NAME'] + '/'
+        else:
+            return '/'
 
     def get_application_static_files_root_url(self):
         # Typical applications will have their static files under the same root
@@ -105,6 +109,26 @@
         # some other path, or even on some totally different hostname.
         return self.get_root_url()
 
+    def get_frontoffice_url(self):
+        frontoffice_url = get_cfg('misc', {}).get('frontoffice-url', None)
+        if frontoffice_url:
+            return frontoffice_url
+        req = self.get_request()
+        if req:
+            return '%s://%s%s' % (req.get_scheme(), req.get_server(),
+                    urllib.quote(req.environ.get('SCRIPT_NAME')))
+        return 'http://%s' % os.path.basename(get_publisher().app_dir)
+
+    def get_backoffice_url(self):
+        backoffice_url = get_cfg('misc', {}).get('backoffice-url', None)
+        if backoffice_url:
+            return backoffice_url
+        req = self.get_request()
+        if req:
+            return '%s://%s%s/backoffice' % (req.get_scheme(), req.get_server(),
+                    urllib.quote(req.environ.get('SCRIPT_NAME')))
+        return 'http://%s/backoffice' % os.path.basename(self.app_dir)
+
     def format_publish_error(self, exc):
         get_response().filter = {}
         if isinstance(exc, errors.AccessError) and hasattr(exc, 'render'):
@@ -387,6 +411,8 @@
         lang = self.cfg.get('language', {}).get('language', None)
         if lang == 'HTTP':
             request = self.get_request()
+            if not request:
+                return None
             lang = None
             accepted_languages = request.get_header('Accept-Language')
             if accepted_languages:
@@ -396,8 +422,7 @@
                 for l in accepted_languages:
                     if l in self.translations.keys():
                         return l
-        else:
-            return lang
+        return lang
 
     def get_admin_module(cls):
         return None
@@ -714,6 +739,8 @@
         return {
             'site_name': get_cfg('misc', {}).get('sitename', None),
             'site_theme': get_cfg('branding', {}).get('theme', self.default_theme),
+            'site_url': self.get_frontoffice_url(),
+            'site_url_backoffice': self.get_backoffice_url(),
             'today': strftime.strftime(misc.date_format(), time.localtime()),
             'now': misc.localstrftime(time.localtime()),
         }
@@ -747,5 +774,7 @@
 
 Substitutions.register('site_name', category=N_('General'), comment=N_('Site Name'))
 Substitutions.register('site_theme', category=N_('General'), comment=N_('Current Theme Name'))
+Substitutions.register('site_url', category=N_('General'), comment=N_('Site URL'))
+Substitutions.register('site_url_backoffice', category=N_('General'), comment=N_('Site URL (backoffice)'))
 Substitutions.register('today', category=N_('General'), comment=N_('Current Date'))
 Substitutions.register('now', category=N_('General'), comment=N_('Current Date & Time'))
Index: wcs/formdef.py
===================================================================
--- wcs/formdef.py	(révision 2232)
+++ wcs/formdef.py	(copie de travail)
@@ -232,11 +232,10 @@
     get_by_urlname = classmethod(get_by_urlname)
 
     def get_url(self, backoffice = False):
-        req = get_request()
-        base_url = '%s://%s%s' % (req.get_scheme(), req.get_server(),
-                urllib.quote(req.environ.get('SCRIPT_NAME')))
         if backoffice:
-            base_url += '/backoffice'
+            base_url = get_publisher().get_backoffice_url()
+        else:
+            base_url = get_publisher().get_frontoffice_url()
         return '%s/%s/' % (base_url, self.url_name)
 
 
Index: wcs/formdata.py
===================================================================
--- wcs/formdata.py	(révision 2232)
+++ wcs/formdata.py	(copie de travail)
@@ -199,11 +199,10 @@
         return wf_status.handle_form(form, self, user)
 
     def get_url(self, backoffice = False):
-        req = get_request()
-        base_url = '%s://%s%s' % (req.get_scheme(), req.get_server(),
-                urllib.quote(req.environ.get('SCRIPT_NAME')))
         if backoffice:
-            base_url += '/backoffice'
+            base_url = get_publisher().get_backoffice_url()
+        else:
+            base_url = get_publisher().get_frontoffice_url()
         return '%s/%s/%s/' % (base_url, self.formdef.url_name, self.id)
 
     def get_workflow_status(self):
@@ -229,6 +228,7 @@
             'form_receipt_time': qommon.strftime.strftime('%H:%M', self.receipt_time),
             'form_number': str(self.id),
             'form_url': self.get_url(),
+            'form_url_backoffice': self.get_url(backoffice=True),
             'form_uri': '%s/%s/' % (self.formdef.url_name, self.id),
             'form_status_url': '%sstatus' % self.get_url(),
             'form_details': self.formdef.get_detailed_email_form(self, self.get_url()),
@@ -307,6 +307,7 @@
 Substitutions.register('form_number', category=N_('Form'), comment=N_('Form Number'))
 Substitutions.register('form_details', category=N_('Form'), comment=N_('Form Details'))
 Substitutions.register('form_url', category=N_('Form'), comment=N_('Form URL'))
+Substitutions.register('form_url_backoffice', category=N_('Form'), comment=N_('Form URL (backoffice)'))
 Substitutions.register('form_uri', category=N_('Form'), comment=N_('Form URI'))
 Substitutions.register('form_status_url', category=N_('Form'), comment=N_('Form Status URL'))
 Substitutions.register('form_user', category=N_('Form'), comment=N_('Form Submitter'))
Index: wcs/qommon/cron.py
===================================================================
--- wcs/qommon/cron.py	(révision 2235)
+++ wcs/qommon/cron.py	(copie de travail)
@@ -62,10 +62,12 @@
                 publisher.app_dir = os.path.join(app_dir, hostname)
                 if not os.path.isdir(publisher.app_dir):
                     continue
+                # create a fake publisher
                 try:
                     publisher.set_config()
                 except:
                     continue
+                publisher.install_lang(publisher.get_site_language())
                 for job in publisher.cronjobs:
                     if job.days and now[2] not in job.days:
                         continue
@@ -75,6 +77,8 @@
                         continue
                     if job.minutes and not now[4] in job.minutes:
                         continue
+                    publisher.substitutions.reset()
+                    publisher.substitutions.feed(publisher)
                     try:
                         job.function(publisher)
                     except:
