Add an i18n Static Page to Symfony

My last post explained the basics on how to add static pages in symfony, this post expands on that and shows you how to do it for a multilingual site.

We split the template finding code out for code maintainability, and we enhance it on where to look for the file. First it tries to find the template in the language and country eg: en_CA, then it tries to find the template in the matching language, and if that is not found, it falls back to the default language.

/**
 * Load a static page.
 * @param sfRequest $request A request object
 */
public function executePage(sfWebRequest $request)
{
  $template = $this->findTemplate($request->getParameter('view'), $this->getUser()->getCulture());
  $this->forward404Unless($template);
  $this->setTemplate($template);
}

/**
 * Check if a template page exists for a given culture.
 * Be intelligent and check if language & country exist, try language, and then default to english.
 * @param string $name Template filename to check
 * @param string $culture Symfony culture string
 */
protected function findTemplate($name, $culture)
{
  // for safety, strip out all non-alphanumeric characters
  $name = preg_replace('/[^a-zA-Z0-9\s]/', '', $name);

  $directory = $this->getContext()->getModuleDirectory() . DIRECTORY_SEPARATOR ."templates";
  // try language and country: en_CA
  if (is_readable($directory . DIRECTORY_SEPARATOR . $culture. DIRECTORY_SEPARATOR . $name ."Success.php"))
  {
    return $culture. DIRECTORY_SEPARATOR . $name;
  }
  // try langage only: en
  elseif (is_readable($directory . DIRECTORY_SEPARATOR . substr($culture, 0, 2). DIRECTORY_SEPARATOR . $name ."Success.php"))
  {
    return substr($culture, 0, 2). DIRECTORY_SEPARATOR . $name;
  }
  // try default language
  elseif (is_readable($directory . DIRECTORY_SEPARATOR . $name ."Success.php"))
  {
    return $name;
  }
  return false;
}

The template directory should have the default language file as usual, eg: templates/helpSuccess.php, and then there should be folders for each language and possibly language & country with the same filename, but localized. eg: templates/fr/helpSuccess.php

Add a Static Page to Symfony

Static pages can be added to Symfony quite easily.

Edit your routing.yml file which is probably located at apps/frontend/config/routing.yml, and add the following routes to add an about, a privacy, and a terms and conditions page.

# static pages
about:
  url:   /about
  param: { module: home, action: page, view: about }
privacy:
  url:   /privacy
  param: { module: home, action: page, view: privacy }
terms:
  url:   /terms
  param: { module: home, action: page, view: terms }

If you are going to keep the generic rules, make sure you add these new rules before the default actions.

Clear your cache:
./symfony clear-cache

Then inside the module named home (create it if it doesn’t exist), add the following action:

  /** 
   * Load a static page.
   * @param sfRequest $request A request object
   */
  public function executePage(sfWebRequest $request)
  {
    $directory = $this->getContext()->getModuleDirectory().DIRECTORY_SEPARATOR."templates";
    $name = $request->getParameter('view');
    // for safety, strip out all non-alphanumeric characters
    $name = preg_replace('/[^a-zA-Z0-9\s]/', '', $name);
    if (is_readable($directory.DIRECTORY_SEPARATOR.$name."Success.php"))
    { 
      return $this->setTemplate($name);
    }
    else
    { 
      $this->forward404();
    }
  }

The template files will be on the home/templates directory, called aboutSuccess.php, privacySuccess.php, and termsSuccess.php

This action will check if the template file exists, and if so load the template, if not it will forward to the 404 not found page. Easy and safe static templates. Add more routes and the appropriate template file as required.

PHP Session Sharing Among Subdomains

PHP supports sharing sessions among subdomains which can be very useful for sharing cookies and sessions among multiple web applications.

I wanted my users to remain logged in while navigating from www.example.com to site1.example.com. By default PHP will treat these domains as two separate sites and users would have to login to each subdomain.

All you have to do is to either set the following setting in your php.ini file:
session.cookie_domain = .example.com

Or, set it from within your code before you create your session:
ini_set("session.cookie_domain", ".example.com");
session_start();

Validating a Facebook Session Within an iFrame

Facebook takes security seriously and there are many many things a Facebook app cannot do. Sometimes it is necessary to create an iFrame and load the page in to work around Facebook’s security model. This example shows how you can continue to use the $facebook->require_login() method while you are inside an iFrame.

Each Facebook request to your application contains a number of parameters that you can use to authenticate if the request really came from Facebook and if the user is actually logged in. These parameters looks like this:

Array
(
    [fb_sig_in_canvas] => 1
    [fb_sig_request_method] => GET
    [fb_sig_friends] => 12345678,123456789,...
    [fb_sig_locale] => en_US
    [fb_sig_in_new_facebook] => 1
    [fb_sig_time] => 1265247656.6432
    [fb_sig_added] => 1
    [fb_sig_profile_update_time] => 1261813927
    [fb_sig_expires] => 1265248900
    [fb_sig_user] => 1234567
    [fb_sig_session_key] => 2.18zPq2sNPEE6sn0wpMLc5w__.3600.1265248800-1234567
    [fb_sig_api_key] => ab6c2d2604ae9604be0efef88315c53e
    [fb_sig_app_id] => 289424642699
    [fb_sig] => 575ed0ccd0c9b3ea2d5d7c5417187de6
)

If you create an iFrame in your facebook page, the parameters will be passed to the iFrame but by will not automatically be passed from one request to the next. Once the user clicks on a link or you try to do an ajax request, you will not be able to validate the user. The simple trick to keep the authorization is to pass the fb_sig_* parameters to request. A handy PHP function like this will help:

function fb_vars()
{
  $fb_vars = array();
  foreach ($_GET as $key => $value)
  {
    if (strpos($key, "fb_sig") !== false)
    {   
      $fb_vars[] = $key."=".$value;
    }   
  }

  return implode('&', $fb_vars);
}

If you append the result of that function to the query string of each request, the $facebook->require_login() call and the $facebook->validate_fb_params() can then successfully validate if your user is from Facebook.