Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "helper"

CSS link class adder

<?php
 
/**
 * Automatically adds a class name to a link (or a parent tag) if the link route matches
 * the current module or route.
 * (based on the work made by scott meves on 2007-02-24 : http://snippets.symfony-project.org/snippet/153)
 *
 * @author  ESCANDELL Stéphane <stephane.escandell[a]gmail.com>
 * @date    2010-07-16
 *
 *
 * @param  <name> : string name of the link, i.e. string to appear between the <a> tags
 * @param  <internal_uri> string 'module/action' or '@rule' of the action
 * @param  <options> array additional style options
 * @return string XHTML compliant <a href> tag (encapsulated by an optionnal tag)
 * @see    link_to
 * 
 *
 *
 * <b>Available Options:</b>
 *    -> All classic options from link_to() method
 *
 *    -> 'moduleOnly'
 *      recognized values:
 *        -> true
 *        -> false
 *      If set to <true>, the link is identify as matched only if the module of the route associated to <internal_uri>
 *      is matching to the current module.
 *      If not set, the default value is false
 *
 *    -> 'currentClass'
 *      recognized values : 
 *        -> string 
 *      The class identifier added to the link (or the parent tag, see below) if <internal_uri> is matching to
 *      the current page.
 *      If not set, the default class added is 'active'.
 *
 *    -> 'separator'
 *      recognized values:
 *        -> string
 *      Used only if the <class> option is set. If the link match, this separator will be used between the <class>
 *      option and <currentClass> option (the generated class will be <class>.<separator>.<currentClass>).
 *      If not set, the default value is a space caracter ' '.
 *
 *    ->  'tag'
 *      recognized values:
 *        -> html tag name
 *      Identify an html tag to encapsulate the link. If this option appears, the <currentClass> name will be added
 *      to this tag rather than the <a> tag.
 *
 *    -> 'tagOptions'
 *      recognized values:
 *        -> array of html parameters
 *      Used only if the parameter <tag> exists. <tagOptions> is used to generate the tag to encapsulate the link with
 *      user specific options.
 *
 * <b>Examples:</b>
 * <code>
 *   <?php echo link_to_styled( 'home',
 *                           '@homepage',
 *                           array('title' => 'Home')); ?>
 *   if the current route is '@homepage', then:
 *     => <a href="path/to/homepage/action" class="active" title="Home">home</a>
 * 
 *   <?php echo link_to_styled( 'home',
 *                           '@homepage',
 *                           array('title' => 'Home', 'currentClass' => 'myActiveClass')); ?>
 *   if the current route is '@homepage', then:
 *     => <a href="path/to/homepage/action" class="myActiveClass" title="Home">home</a>
 *
 *   <?php echo link_to_styled( 'home',
 *                           '@homepage',
 *                           array('title' => 'Home', 'separator' => '_')); ?>
 *   if the current route is '@homepage', then:
 *     => <a href="path/to/homepage/action" class="active" title="Home">home</a>
 *
 *   <?php echo link_to_styled( 'home',
 *                           '@homepage',
 *                           array('title' => 'Home', 'class' => 'myClass tab', 'separator' => '_')); ?>
 *   if the current route is '@homepage', then:
 *     => <a href="path/to/homepage/action" class="myClass tab_active" title="Home">home</a>
 *
 *   <?php echo link_to_styled( 'home',
 *                           '@homepage',
 *                           array('title' => 'Home', 'currentClass' => 'myActiveClass',
 *                                 'tag' => 'li')); ?>
 *   if the current route is '@homepage', then:
 *     => <li class="myActiveClass"><a href="path/to/homepage/action" title="Home">home</a></li>
 *
 *   <?php echo link_to_styled( 'home',
 *                           '@homepage',
 *                           array('title' => 'Home',
 *                                 'tag' => 'li',
 *                                  'tagOptions' => array('class' => 'anotherClass',
 *                                                        'width' => '250px')
 *                                )); ?>
 *   if the current route is '@homepage', then:
 *     => <li class="anotherClass active" width="250px"><a href="path/to/homepage/action" title="Home">home</a></li>
 *
 *   <?php echo link_to_styled( 'home',
 *                           '@homepage',
 *                           array('title' => 'Home',
 *                                 'currentClass' => myActiveClass,
 *                                 'tag' => 'li',
 *                                  'tagOptions' => array('class' => 'anotherClass other',
 *                                                        'width' => '250px')
 *                                )); ?>
 *   if the current route is '@homepage', then:
 *     => <li class="anotherClass other myActiveClass" width="250px"><a href="path/to/homepage/action" title="Home">home</a></li>
 * </code>
 *
 *
 * Changelog :
 *    2010/07/16 :
 *      Author : ESCANDELL Stéphane <stephane.escandell[a]gmail.com>
 *      Modification :
 *        First version (based on the work made by scott meves on 2007-02-24 : http://snippets.symfony-project.org/snippet/153)
 */
function link_to_styled($name, $internal_uri, $options = array())
{
  static $context = null;
  static $currentModuleName = null;
  static $applicationConfiguration = null;
 
  if(is_null($context))
  {
    $context = sfContext::getInstance();
  }
  if(is_null($currentModuleName))
  {
    $currentModuleName = $context->getModuleName();
  }
  if(is_null($applicationConfiguration))
  {
    $applicationConfiguration = $context->getConfiguration();
  }
 
  $options    = _parse_attributes($options);
  $separator  = isset($options['separator']) ? $options['separator'] : ' ';
  $tag        = isset($options['tag']) ? $options['tag'] : null;
  $tagOptions = isset($options['tagOptions']) ? $options['tagOptions'] : array();
 
  $isCurrent = false;
  // In order to findRoute() works, we need to delete the reference to
  // the file controller used
  //
  $route = $context->getRouting()->findRoute( str_replace('/'.$applicationConfiguration->getApplication().'_'.$applicationConfiguration->getEnvironment().'.php',
                                                      '',
                                                      url_for($internal_uri,false)
                                                      ));
  if(isset($options['moduleOnly']) && true===$options['moduleOnly'])
  {
    $isCurrent = isset($route['parameters']) ? (isset($route['parameters']['module']) ? $route['parameters']['module']==$currentModuleName : false) : false;
  }
  else
  {
    $isCurrent = url_for($internal_uri,false) == url_for($context->getRouting()->getCurrentInternalUri(),false);
  }
 
  if($isCurrent)
  {
    if(is_null($tag))
    {
      if(!isset($options['class']))
        $options['class'] = isset($options['currentClass']) ? $options['currentClass'] : 'active';
      else
        $options['class'] .= isset($options['currentClass']) ? $separator.$options['currentClass'] : $separator.'active';
    }
    else
    {
      if(!isset($tagOptions['class']))
        $tagOptions['class']  = isset($options['currentClass']) ? $options['currentClass'] : 'active';
      else
        $tagOptions['class'] .= isset($options['currentClass']) ? $separator.$options['currentClass'] : $separator.'active';
    }
  }
 
 
  unset($options['moduleOnly'],$options['currentClass'],$options['separator'],$options['tag'],$options['tagOptions']);
 
  if(is_null($tag))
    return link_to($name, $internal_uri, $options);
  else
    return content_tag($tag, link_to($name, $internal_uri, $options), $tagOptions);
}
 
by Stéphane Escandell on 2010-07-16, tagged css  helper  link  menu  navigation 
(2 comments)

String Slug

<?php
 
function slugString($string, $replacement = '_')
{
  $aux = preg_quote($replacement, '/');
 
  $map = array(
    '/à|á|ã|â/' => 'a',
    '/è|é|ê|ẽ|ë/' => 'e',
    '/ì|í|î/' => 'i',
    '/ò|ó|ô|õ|ø/' => 'o',
    '/ù|ú|ũ|û/' => 'u',
    '/ç/' => 'c',
    '/ñ/' => 'n',
    '/ä|æ/' => 'ae',
    '/ö/' => 'oe',
    '/ü/' => 'ue',
    '/Ä/' => 'Ae',
    '/Ü/' => 'Ue',
    '/Ö/' => 'Oe',
    '/ß/' => 'ss',
    '/[^\w\s]/' => ' ',
    '/\\s+/' => $replacement
  );
 
  return preg_replace(array_keys($map), array_values($map), $string);
}
 

based on http://api.cakephp.org/view_source/inflector/#line-480

by Raphael Araújo on 2009-10-16, tagged helper  slug  text 

Switch culture from any page of your site and improve SEO

CONTEXT

Symfony internationalization is normally managed using session variables. To switch culture you can visit a page that first changes you session variable and then send you back to the page you were visiting, but this potentially breaks your SEO.

Adding culture in every URL is a better solution, but if you want to go back to the page you were visiting, you have to deal with redirection by yourself.

This widget solves the redirection issue by creating localized urls.

CODE

Save your file here: apps/appname/lib/helper/I18nUrlHelper.php

/*
 * Generate a localized version for the URL you are visiting.
 */
 
function localized_current_url($sf_culture = null)
{
  if (! $sf_culture)
  {
    throw new sfException(sprintf('Invalid parameter $sf_culture "%s".', $sf_culture));
  }
 
  $routing    = sfContext::getInstance()->getRouting();
  $request    = sfContext::getInstance()->getRequest();
  $controller = sfContext::getInstance()->getController();
 
  // depending on your routing configuration, you can set $route_name = $routing->getCurrentRouteName()
  $route_name = '';
 
  $parameters = $controller->convertUrlStringToParameters($routing->getCurrentInternalUri());
  $parameters[1]['sf_culture'] = $sf_culture;
 
  return $routing->generate($route_name, array_merge($request->getGetParameters(), $parameters[1]));
}
 

EXAMPLE

Obviously, you must add the :sf_culture token in every rule of your application routing.yml:

product_page:
  url:   /:sf_culture/product/:id
  requirements: { sf_culture: (?:en|es|fr|it|de) }
  param: { module: product, action: show }
 
homepage:
  url:   /:sf_culture
  requirements: { sf_culture: (?:en|es|fr|it|de) }
  param: { module: page, action: home }

default_symfony:
  url:   /symfony/:sf_culture/:action/*
  requirements: { sf_culture: (?:en|es|fr|it|de) }
  param: { module: default }
 
default_index:
  url:   /:sf_culture/:module
  param: { action: index }
  requirements: { sf_culture: (?:en|es|fr|it|de) }
 
default:
  url:   /:sf_culture/:module/:action/*
  requirements: { sf_culture: (?:en|es|fr|it|de) }
 

This code let you create a list of link to the localized versions. The list of languages/cultures is taken from your config file.

<div>
  <?php foreach(sfConfig::get('app_languages_available') as $language) { ?>
    <a href="<?php echo localized_current_url($language); ?>"><?php echo $language; ?></a>  
  <?php } ?>
</div>
 
by Davide Fedrigo on 2009-09-28, tagged culture  helper  i18n  navigation  routing 
(3 comments)

jQuery AdminDoubleList replacements

jQuery AdminDouble List

These two js functions do the job while using jQuery with admin double list

function double_list_move(src, dest)
{
    var L = $(src).children();
    $(L).each(function(i){
        if(L[i].selected){
            $(dest).append('<option value="'+$(L[i]).val()+'">'+ $(L[i]).text()+'</option>');
            $(L[i]).remove();
        }
    });
 
}
 
function double_list_submit()
{
    var sel = $("form").find("select.sf_admin_multiple-selected");
    var C = $(sel).children();
    $(C).each(
        function(i){
            if(!C[i].selected)
                C[i].selected=true;
        }
    );
 
}
 

Enjoy it.

by Thomas Schäfer on 2008-03-19, tagged helper  javascript  jquery 

Phone Number Helper

format a phone number with 7, 10, or 11 digits. also can convert phone number letters to numbers

lib/helpers/PhoneHelper.php

<?php 
 
function format_phone($phone = '', $convert = false, $trim = true)
{
    // If we have not entered a phone number just return empty
    if (empty($phone)) {
        return '';
    }
 
    // Strip out any extra characters that we do not need only keep letters and numbers
    $phone = preg_replace("/[^0-9A-Za-z]/", "", $phone);
 
    // Do we want to convert phone numbers with letters to their number equivalent?
    // Samples are: 1-800-TERMINIX, 1-800-FLOWERS, 1-800-Petmeds
    if ($convert == true) {
        $replace = array('2'=>array('a','b','c'),
                 '3'=>array('d','e','f'),
                     '4'=>array('g','h','i'),
                 '5'=>array('j','k','l'),
                                 '6'=>array('m','n','o'),
                 '7'=>array('p','q','r','s'),
                 '8'=>array('t','u','v'),                                '9'=>array('w','x','y','z'));
 
        // Replace each letter with a number
        // Notice this is case insensitive with the str_ireplace instead of str_replace 
        foreach($replace as $digit=>$letters) {
            $phone = str_ireplace($letters, $digit, $phone);
        }
    }
 
    // If we have a number longer than 11 digits cut the string down to only 11
    // This is also only ran if we want to limit only to 11 characters
    if ($trim == true && strlen($phone)>11) {
        $phone = substr($phone, 0, 11);
    }                        
 
    // Perform phone number formatting here
    if (strlen($phone) == 7) {
        return preg_replace("/([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/", "$1-$2", $phone);
    } elseif (strlen($phone) == 10) {
        return preg_replace("/([0-9a-zA-Z]{3})([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/", "($1) $2-$3", $phone);
    } elseif (strlen($phone) == 11) {
        return preg_replace("/([0-9a-zA-Z]{1})([0-9a-zA-Z]{3})([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/", "$1($2) $3-$4", $phone);
    }
 
    // Return original phone if not 7, 10 or 11 digits long
    return $phone;
}
 
by Alex Zogheb on 2008-02-08, tagged formatting  helper  javascript  number  phone  regex 
(1 comment)

Accordion Helper

A symfony helper for the stickman labs accordion http://www.stickmanlabs.com/accordion/

hopefully plugin oneday

<?php
 
use_helper('Tag', 'Javascript');
 
function accordion($container, $options = array()){
 
    $response = sfContext::getInstance()->getResponse();
    $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/prototype');
    $response->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/effects');
    $response->addJavascript('accordion', 'last');
 
    $options = _parse_attributes($options);
    $on_load = (isset($options['on_load']) && $options['on_load'] == false) ? false : true;
    if(isset($options['use_stylesheet']) && $options['use_stylesheet']==true) $response->addStylesheet('accordion');
 
    $output= '';
    //onLoad
    $output .= $on_load ? "Event.observe(window, 'load', function() {" : '';
 
    //new accordion
    $output .= "var accordion_$container = new accordion ('$container', ";
 
 
    $accordion_options = array();
 
    //speed
    if (isset($options['resize_speed'])) $accordion_options['resizeSpeed'] = $options['resize_speed'];
 
    //classes
    if(isset($options['toggle']) || isset($options['toggle_active']) || isset($options['content'])){
        if(isset($options['toggle'])) $accordion_options['classNames']['toggle'] = "'{$options['toggle']}'";
        if(isset($options['toggle_active'])) $accordion_options['classNames']['toggleActive'] = "'{$options['toggle_active']}'";
        if(isset($options['content'])) $accordion_options['classNames']['content'] = "'{$options['content']}'";
        $accordion_options['classNames'] = _options_for_javascript_no_sort($accordion_options['classNames']);
    }
 
    //size
    if(isset($options['height']) || isset($options['width'])){
        if(isset($options['height'])) $accordion_options['defaultSize']['height'] = $options['height'];
        if(isset($options['width'])) $accordion_options['defaultSize']['width'] = $options['width'];
        $accordion_options['defaultSize'] = _options_for_javascript_no_sort($accordion_options['defaultSize']);
    }
 
    //direction
    if (isset($options['direction'])) $accordion_options['direction'] = "'{$options['direction']}'";    
 
    //event
    if (isset($options['on_event'])) $accordion_options['onEvent'] = "'{$options['on_event']}'";
 
    $output .= _options_for_javascript_no_sort($accordion_options);
 
    //new accordion end
    $output .= ");";
 
    if(isset($options['activate'])){
        $number = $options['activate'];
        $output .= "accordion_$container.activate($$('#$container .{$options['toggle']}')[$number]);";
    }
 
    //onLoad
    $output .= $on_load ? "});" : '';
 
    return javascript_tag($output);
 
}
 
  function _options_for_javascript_no_sort($options)
  {
    $opts = array();
    foreach ($options as $key => $value)
    {
      $opts[] = "$key:$value";
    }
 
    return '{'.join(', ', $opts).'}';
  }
 
by Alex Zogheb on 2008-02-08, tagged accordion  helper  javascript 

Filter item in admin_check_list using admin generator.

Hi, this is my first snippet (in my first Symfony project), so I hope this is useful and not boring for you. I show you my code to solve a problem: how to filter an admin_check_list using a criteria.

In my project I have in the schema also this tables:

  role:
    type:                { primaryKey: true, type: char }
    descritpion:         { type: varchar(50), required: true }
  user:
    id:
    username:            { type: varchar(100), index: unique, required: true }
    role_type:           { type: char, foreignTable: role, foreignReference: type, required: true }
  course:
    id:
    name:                { type: varchar(255), required: true }
    research_proposal:   { type: longvarchar }
  course_teacher:
    course_id:
    user_id:
 

Little explanation: Each user have a role. Each course can have more teacher; each teacher is an user with "T" role_type.

In the generator.yml I can write

edit:
  fields:
    course_teachers:         { type: admin_check_list, params: through_class=CourseTeacher }
 

then in the course creation page I have a list of all users (and not a list of the teacher).

To reach my scope I need to override the ObjectAdminHelper. I create a file named ObjectAdminHelper.php and put it into the /lib/helper folder of my Symfony folder. To override successfully an helper it is necessary to overwrite (or write) all its method. So just copy all from the original ObjectAdminHelper.php file and paste it into this new file. The only method we need to override is "_get_object_list($object, $method, $options, $callback)": it use by default the method "_get_propel_object_list" to get the list to show... But if we would like to modify that list how can we do?

function _get_object_list($object, $method, $options, $callback){
  $object = get_class($object) == 'sfOutputEscaperObjectDecorator' ? $object->getRawValue() : $object;
  // the default callback is the propel one
  if (!$callback) {
    $callback = _get_option($options, 'callback');
    if (!$callback) {
        $callback = '_get_propel_object_list';
    }
  }
  return call_user_func($callback, $object, $method, $options);
}
 

This method get an option called "callback" to get the name of the callback method. If a callback method is not present it use the standard "_get_propel_object_list".

So if I would like to get only the teacher (role_type="T") i can write this function (very similar than _get_propel_object_list):

function _get_teacher_from_users($object, $method, $options){
    $criteria = new Criteria();
    $criteria->add(UserPeer::ROLE_TYPE, "T");
    $through_class = _get_option($options, 'through_class');
    $objects = sfPropelManyToMany::getAllObjects($object, $through_class, $criteria);
    $objects_associated = sfPropelManyToMany::getRelatedObjects($object, $through_class, $criteria);
    $ids = array_map(create_function('$o', 'return $o->getPrimaryKey();'), $objects_associated);
    return array($objects, $objects_associated, $ids);
}
 

Now you can write all the function you need to filter the lists.

Last step: how can my generator.yml say to the helper which method it must use? Simply modify the course_teacher row:

edit:
  fields:
    course_teachers:         { type: admin_check_list, params: through_class=CourseTeacher callback=_get_teacher_from_users }
 

I hope this is useful for you (and sorry for my english).

Pierpaolo Cira

by Pierpaolo Cira on 2008-01-21, tagged adminchecklist  filter  helper 

Integration of the TinyFCK widget

There are two big open source rich text editors:

Some people prefer the first, some other the second, but there’s a problem for who likes to use TinyMCE: it lacks of a free file manager/uploader.

What we actually need is to have a TinyMCE installation with a free file manager/uploader, thus we take it from FCKEditor.

This is TinyFCK: a TinyMCE with the FCKEditor’s file manager/uploader, nothing less, nothing more.

Installation of TinyFCK rich text editing is described here. You need to download the editor from the project website (http://p4a.crealabsfoundation.org/tinyfck) and unpack it in a temporary folder. Copy the tiny_fck/ directory into your project web/js/ directory, and define the path to the library and new rich editor class in settings.yml.

all:
  .settings:
    rich_text_editor_class: TinyFCK
    rich_text_js_dir:  js/tiny_fck
 

After that create new text editor class file sfRichTextEditorTinyFCK.class.php in your project or application lib/ folder.

<?php
 
/*
 * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
 * (c) 2007 Eugene Krasichkov <megazlo@megazlo.com>
 *  
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
 
/**
 * sfRichTextEditorTinyFCK implements the TinyFCK rich text editor.
 *
 * <b>Options:</b>
 *  - css - Path to the TinyFCK editor stylesheet
 *
 *    <b>Css example:</b>
 *    <code>
 *    / * user: foo * / => without spaces. 'foo' is the name in the select box
 *    .foobar
 *    {
 *      color: #f00;
 *    }
 *    </code>
 *
 */
class sfRichTextEditorTinyFCK extends sfRichTextEditor
{
  /**
   * Returns the rich text editor as HTML.
   *
   * @return string Rich text editor HTML representation
   */
  public function toHTML()
  {
    $options = $this->options;
 
    // we need to know the id for things the rich text editor
    // in advance of building the tag
    $id = _get_option($options, 'id', $this->name);
 
    // use tinymce's gzipped js?
    $tinymce_file = _get_option($options, 'tinymce_gzip') ? '/tiny_mce_gzip.php' : '/tiny_mce.js';
 
    // tinymce installed?
    $js_path = sfConfig::get('sf_rich_text_js_dir') ? '/'.sfConfig::get('sf_rich_text_js_dir').$tinymce_file : '/sf/tinymce/js'.$tinymce_file;
    if (!is_readable(sfConfig::get('sf_web_dir').$js_path))
    {
      throw new sfConfigurationException('You must install TinyFCK to use this helper (see rich_text_js_dir settings).');
    }
 
    sfContext::getInstance()->getResponse()->addJavascript($js_path);
 
    use_helper('Javascript');
 
    $tinymce_options = '';
    $style_selector  = '';
 
    // custom CSS file?
    if ($css_file = _get_option($options, 'css'))
    {
      $css_path = stylesheet_path($css_file);
 
      sfContext::getInstance()->getResponse()->addStylesheet($css_path);
 
      $css    = file_get_contents(sfConfig::get('sf_web_dir').DIRECTORY_SEPARATOR.$css_path);
      $styles = array();
      preg_match_all('#^/\*\s*user:\s*(.+?)\s*\*/\s*\015?\012\s*\.([^\s]+)#Smi', $css, $matches, PREG_SET_ORDER);
      foreach ($matches as $match)
      {
        $styles[] = $match[1].'='.$match[2];
      }
 
      $tinymce_options .= '  content_css: "'.$css_path.'",'."\n";
      $tinymce_options .= '  theme_advanced_styles: "'.implode(';', $styles).'"'."\n";
      $style_selector   = 'styleselect,separator,';
    }
 
    $culture = sfContext::getInstance()->getUser()->getCulture();
 
    $tinymce_js = '
tinyMCE.init({
  mode: "exact",
  language: "'.strtolower(substr($culture, 0, 2)).'",
  elements: "'.$id.'",
  plugins : "table,save,advhr,advimage,advlink,emotions,iespell,insertdatetime,preview,zoom,flash,searchreplace,print,paste,directionality,fullscreen,noneditable,contextmenu",
  theme: "advanced",
  theme_advanced_buttons1_add_before : "save,newdocument,separator",
    theme_advanced_buttons1_add : "fontselect,fontsizeselect",
    theme_advanced_buttons2_add : "separator,insertdate,inserttime,preview,zoom,separator,forecolor,backcolor,liststyle",
    theme_advanced_buttons2_add_before: "cut,copy,paste,pastetext,pasteword,separator,search,replace,separator",
    theme_advanced_buttons3_add_before : "tablecontrols,separator",
    theme_advanced_buttons3_add : "emotions,iespell,flash,advhr,separator,print,separator,ltr,rtl,separator,fullscreen",
    theme_advanced_toolbar_location : "top",
    theme_advanced_toolbar_align : "left",
    theme_advanced_statusbar_location : "bottom",
  theme_advanced_resizing : true,
  theme_advanced_resize_horizontal : false,
  plugin_insertdate_dateFormat : "%Y-%m-%d",
  plugin_insertdate_timeFormat : "%H:%M:%S",
  file_browser_callback : "tinyFCKFileBrowserCallBack",
  paste_use_dialog : false,
  extended_valid_elements: "img[class|src|border=0|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name]",
  apply_source_formatting : true,
  relative_urls: false,
  debug: false
  '.($tinymce_options ? ','.$tinymce_options : '').'
  '.(isset($options['tinymce_options']) ? ','.$options['tinymce_options'] : '').'
});
 
  function tinyFCKFileBrowserCallBack(field_name, url, type, win) {
    var connector = "../../filemanager/browser.html?Connector=connectors/php/connector.php";
    var enableAutoTypeSelection = true;
 
    var cType;
    tinyfck_field = field_name;
    tinyfck = win;
 
    switch (type) {
      case "image":
        cType = "Image";
        break;
      case "flash":
        cType = "Flash";
        break;
      case "file":
        cType = "File";
        break;
    }
 
    if (enableAutoTypeSelection && cType) {
      connector += "&Type=" + cType;
    }
 
    window.open(connector, "tinyfck", "modal,width=600,height=400");
  }';
 
    if (isset($options['tinymce_options']))
    {
      unset($options['tinymce_options']);
    }
 
    return
      content_tag('script', javascript_cdata_section($tinymce_js), array('type' => 'text/javascript')).
      content_tag('textarea', $this->content, array_merge(array('name' => $this->name, 'id' => get_id_from_name($id, null)), _convert_options($options)));
  }
}
 

Then clear cache:

symfony cc
 

Once this is done, toggle the use of rich text editing in text areas by adding the rich=true option. You can also specify custom options for the TinyFCK editor using the tinymce_options option.

<?php echo textarea_tag('name', 'default content', 'rich=true size=10x20')) ?>
 => a rich text edit zone powered by TinyFCK
<?php echo textarea_tag('name', 'default content', 'rich=true size=10x20tinymce_options=language:"fr",theme_advanced_buttons2:"separator"')) ?>
=> a rich text edit zone powered by TinyFCK with custom parameters
 
by Eugene Krasichkov on 2007-12-27, tagged editor  fckeditor  form  forms  helper  rich  tinyfck  tinymce 
(1 comment)

jsValidator integration helper

A real client-side validation with some nice features, without Ajax tricks provided by sfYzAjaxValidationPlugin may be easily done using jsValidator (http://sourceforge.net/projects/jsformutils/) and little helper that follows.

<?php
/**
 * Generates JavaScript code to validate a form using 
 * jsValidator as client-side validation engine (see http://sourceforge.net/projects/jsformutils/)
 * 
 * @param string $targetForm id attribute of the form to be validated
 * @param mixed $options associative array of options
 * @param string $action action to validated, defaults to current
 * 
 * Options are:
 *  stopOnFirstError: boolean, default: false
 *  labelMessageDelimiter: string, default: ' : ',
 *  messageSeparator: string, default: "\n",
 *  messageHeader: string, default: 'These fields are invalid:\n---\n' + 
 *  highlightErrors: boolean, whether to mark erroneous fields 
 *  errorElementClass: string, CSS class name to be applied to wrong fields
 *  highlightLabels: boolean, whether to mark fields or fields' labels
 */
function generate_validator($targetForm, $options, $action)
{
  $NL = "\n";
  $funcPrefix = 'validate_';
  $labelsKey = 'labels';
  $fieldsKey = 'fields';
  $jsCode = '';
 
  $paramHolder = sfContext::getInstance()->getRequest()->getParameterHolder();  
  $rulesFilePath = sfConfig::get('sf_app_module_dir').'/'.$paramHolder->get('module').'/'.
    sfConfig::get('sf_app_module_validate_dir_name').'/';
 
  // Load rules from YAML file.   
  if (file_exists($rulesFilePath.$paramHolder->get('action').'.yml'))
    $rules = sfYaml::load($rulesFilePath.$paramHolder->get('action').'.yml');
  else
    $rules = sfYaml::load($rulesFilePath.$action.'.yml');
 
  // Generate jsValidator compliant rules.  
  $jsRules = array();
  foreach ($rules['fields'] as $fieldId => $validationRule)
  {
    foreach ($validationRule as $validator=>$rule)
    {
      // Remove server-side sfCallbackValidator.
      if ($validator == 'sfCallbackValidator')
      {
        unset($validationRule[$validator]);
        continue;
      }
      // Map Symfony validators to jsValidator.
      $jsvalidator = preg_replace('/^sf(\w+Validator)$/', 'js\\1', $validator);
      if ($jsvalidator != $validator)
      {
        $validationRule[$jsvalidator] = $validationRule[$validator];
        unset($validationRule[$validator]); 
      }
    }
    $jsRules[] = array_merge ( 
      array ('field' => $fieldId, 'label' => $rules['labels'][$fieldId]),
      $validationRule
    );    
  }
 
  // Generate final JavaScript code. 
  $jsCode .= 'function '.$funcPrefix.$targetForm.'()'.$NL;
  $jsCode .= 
  '{'.$NL.
  ' var options = '.json_encode($options).';'.$NL.
  ' var rules = '.json_encode($jsRules).';'.$NL.
  ' var jsv = new jsValidator();'.$NL.
  ' jsv.SetOptions(options);'.$NL.$NL.
  ' if (!jsv.Validate(rules))'.$NL.
  ' {'.$NL.
  '   alert(jsv.GetErrorMessage());'.$NL.
  '   return false;'.$NL.  
  ' }'.$NL.
  ' return true;'.$NL.  
  '}'.$NL;
 
  return javascript_tag($jsCode);
}
?>
 

However, I need to clear this out. Besides placing this code into jsValidatorHelper.php either in modules' lib directory or symfony's one, we need to call it properly in the corresponding view.

First of all, include the helper (use JavaScript as well, jsValidator depends on it)

<?php use_helper('JavaScript', 'jsValidator') ?>
 

Validation is performed based on the same simple rule mechanism that Symfony provides. The only difference is that JavaScript validator needs these rules to be in JSON format and it needs some more options, that configure it's behavior.

<?php
// Here we set validation options.
// For more information please refer to documentation of jsValidator
$options = array(
  'stopOnFirstError' => false,
  'labelMessageDelimiter' => ' : ',
  'messageSeparator' => "\n",
  'messageHeader' => "These fields are invalid:\n---\n",
  'highlightErrors' => true,
  'errorElementClass' => 'errClass',
  'highlightLabels' => true
  ); 
// Output auto-generated JavaScript code.
echo generate_validator('editComment', $options, 'update'); 
?>
 

We also have to set up form to point to our validator before submitting. Note, that callback in onsubmit is named by concatenating "validate_" and form's id attribute.

<?php echo form_tag('comment/update', array('id'=>'editComment','onsubmit' => 'return validate_editComment()')) ?>
 

Each field must be somehow identified in the resulting error message. We achieve this by adding some extra information to <action>.yml configuration file.

# define labels for erroneous fields
labels:
  <field_id>: <field_label_text>
 

There is a limitation to validation *.yml file structure. The syntax should be something like this:

# define labels for erroneous fields
labels:
  author: Author
  email: E-mail
  body: Body
 
fields:
  author:
    required:
      msg: The name field cannot be left blank
  email:
    sfEmailValidator:
      email_error: The email address is not valid.
  body:
    required:
      msg: The text field cannot be left blank
 
fillin:
  enabled:       on
 

And finally, don't forget to place jsValidator into /web/js folder, and include it in view.yml

<actionTemplate>:
  javascripts: [jsvalidator]
 

Feel free to modify the snippet code and validator to achieve best results!

by Alex Oroshchuk on 2007-12-04, tagged helper  javascript  validation 
(1 comment)

antispam helper for mailto link

Provide 4 functions which crypt e-mail address with javascript in order to not be captured by spammers robots.

by Sylvain PAPET on 2007-11-19, tagged email  helper  spam 

Delayed javascript page redirect

In order to delay a page redirect with several seconds, I wrote this simple helper

<?php
  use_helper('Javascript');
 
  /**
   * Adds javascript code to delay a page redirect
   *
   * @param string 'module/action' or '@rule' of the action (same argument as url_for())
   * @param int time of delay in seconds. Default = 5
   * @return JavaScript tag for delayed page redirect
   */
  function delayed_redirect($internal_uri, $time = 5)
  {
    sfContext::getInstance()->getResponse()->addJavascript(sfConfig::get('sf_prototype_web_dir').'/js/prototype');
    $code = 'new PeriodicalExecuter(function() { location.href=\''.url_for($internal_uri).'\';}, '.$time.')';
 
    return javascript_tag($code);
  }
 

See http://www.symfony-project.com/book/1_0/07-Inside-the-View-Layer#Adding%20Your%20Own%20Helpers for information on how to add your own helper

by wtreur on 2007-10-04, tagged helper  javascript  redirect 

get cached Thumbnail helper

This helper return path to a generate (and cached) thumbnail for an image and sizes given.

First it checks if the thumbnail as ever been created for the given size. If not, it created it and return the path to the thumbnail. Else, it will only return the path to the thumbnail.

Thumbnails are stored in a sub-directory of the original image named like [width]x[height].

examples : product/foobar.jpg which is 640x480 images - getThumbnail (320x320) will the first time generate the thumbnail "product/320x320/foobar.jpg", and return the path to this image.

parameters : - $image_path should be the path and filename of the image under uploads directory. ex: product/foobar.jpg - $width is the maximal thumbnail width - $height is the maximal thumbnail height

function getThumbnail($image,$width=null,$height=null, $scale = true, $inflate = true, $quality = 75)
{
    $image_dir=dirname($image);
    $image_file=basename($image);
    $thumbnail_dir='';
    if ($width>0) $thumbnail_dir.=$width;
    if ($height>0) $thumbnail_dir.='x'.$height;
    if ($width>0 || $height>0) $thumbnail_dir.='/';
    if (!file_exists(sfConfig::get('sf_upload_dir').'/'.$image_dir.'/'.$thumbnail_dir.$image_file) && ($width!=null || $height!=null))
    {
      if (!is_dir(sfConfig::get('sf_upload_dir').'/'.$image_dir.'/'.$thumbnail_dir))
      {
        mkdir (sfConfig::get('sf_upload_dir').'/'.$image_dir.'/'.$thumbnail_dir,0777);
      }
 
      $thumbnail = new sfThumbnail($width, $height,$scale,$inflate,$quality); 
      $thumbnail->loadFile(sfConfig::get('sf_upload_dir').'/'.$image_dir.'/'.$image_file);
      $thumbnail->save(sfConfig::get('sf_upload_dir').'/'.$image_dir.'/'.$thumbnail_dir.$image_file);
    }
    return '/uploads'.'/'.$image_dir.'/'.$thumbnail_dir.$image_file;
}
 

This is very usefull to use it in model like this :

In model :

  public function getThumbnail($width=null,$height=null)
  {
    sfLoader::loadHelpers('Thumbnail');
    return getThumbnail('product/'.$this->getImage(),$width,$height);
  }
 

in template :

<?php foreach ($products as $product) { ?>
<li><?php echo image_tag($product->getThumbnail(150,100)); ?></li>
<?php } ?>
 

limitation : - work on images of upload directories only (other dir might have permission problem) - the check of thumbnail existance don't take care of scale and inflate values. It is easy to update the code to stored thumbnail in different subdirectory according to these parameters.

by Sylvain PAPET on 2007-09-23, tagged cache  helper  thumbnail 
(1 comment)

Work with IE conditional comments

sometimes you may need to include a stylesheet only for IE, the best effort you give, this box-model gives you headaches and there is no other solution. Same issue for JS files (but it should not happen anymore thanks to prototype).

In these cases you can simply add this in your <head> section

<head>

<?php include_http_metas() ?>
<?php include_metas() ?>

<?php include_title() ?>

<link rel="shortcut icon" href="/favicon.ico" />
<?php include_javascripts() ?>
<?php include_stylesheets() ?>

<!--[if IE 7]>
<?php echo stylesheet_tag('my-css-for-IE7') ?>
<![endif]-->
<!--[if lte IE 6]>
<?php echo stylesheet_tag('my-css-for-IE6-or-lower') ?>
<![endif]-->
</head>

I made a helper for this, so your header will look more like this :

<?php use_helper('IE') ?>
<?php use_ie_stylesheet('my-css-for-IE7', 'version=7') ?>
<?php use_ie_stylesheet('my-css-for-IE6-or-lower', 'version=6 operator=lte') ?>
...
<head>

<?php include_http_metas() ?>
<?php include_metas() ?>

<?php include_title() ?>

<link rel="shortcut icon" href="/favicon.ico" />
<?php include_javascripts() ?>
<?php include_stylesheets() ?>
<?php include_ie_metas() ?>
</head>

Code is not shorter, but it's more "symfony-like" as you don't explicitly write yourself the strange-syntax comments and don't have to remember their (strange) syntax anymore.

The ideal way would be adding a filter inspired by "lib/symfony/sfCommonFilter.class.php" so we would not even have to call the include_* functions ;)

IMPORTANT :

If you do this (using IE helper or not) to OVERRIDE some CSS rules, don't forget that Symfony automagically adds your CSS and JS files just before </head>, so your CSS will be inserted BEFORE all the CSS included via "use_stylesheet()". So you have to call

<?php include_stylesheets() ?>
<?php include_javascripts() ?>

before adding your conditional comments.

And the code of the helper, to be written in "lib/helper/IEHelper.php"

<?php
 
function get_opening_ie_conditional_comments($version = null, $operator = null) {
    return '<!--[if ' . ($version ? $operator.' ' : '') . 'IE' . ($version ? ' '.$version : '') . ']>';
}
 
function get_closing_ie_conditional_comments($version = null, $operator = 'lte') {
    return '<![endif]-->';
}
 
function get_ie_conditional_comments($version = null, $operator = null) {
    return array(
        get_opening_ie_conditional_comments($version, $operator),
        get_closing_ie_conditional_comments($version, $operator)
    );
}
 
function get_ie_metas($type = null) {
    if ($type == 'javascripts' || $type == 'stylesheets') {
        $metas = '';
        $already_seen = array();
        // Stylesheets
        $assets = sfContext::getInstance()->getResponse()->getParameter($type, array(), 'ie');
        foreach ($assets as $asset_info) {
            list($asset, $options) = $asset_info;
            $file = $type == 'stylesheets' ? stylesheet_path($asset) : javascript_path($asset);
            if (isset($already_seen[$file])) {
                continue;
            } else {
                $already_seen[$file] = true;
            }
            // IE version (default : none)
            $version = isset($options['version']) ? $options['version'] : null;
            unset($options['version']);
            // IE version comparison operator (default : "le")
            $operator = isset($options['operator']) ? $options['operator'] : null;
            unset($options['operator']);
            // get corresponding comments
            list($start_comment, $end_comment) = get_ie_conditional_comments($version, $operator);
            $include = $type == 'stylesheets' ? stylesheet_tag($file, $options) : javascript_include_tag($file, $options);
            $metas .= $start_comment . "\n" . $include . $end_comment . "\n";
        }
        return $metas;
    } elseif (is_null($type)) {
        return get_ie_metas('javascripts') . "\n" . get_ie_metas('stylesheets');
    }
}
 
function include_ie_metas() {
    echo get_ie_metas();
}
 
function use_ie_stylesheet($stylesheet, $options = array()) {
    $stylesheets = sfContext::getInstance()->getResponse()->getParameter('stylesheets', array(), 'ie');
    if (!is_array($options)) $options = sfToolkit::stringToArray($options);
    $stylesheets[] = array($stylesheet, $options);
    sfContext::getInstance()->getResponse()->setParameter('stylesheets', $stylesheets, 'ie');
}
 
function include_ie_stylesheets() {
    echo get_ie_stylesheets();
}
 
function get_ie_stylesheets() {
    return get_ie_metas('stylesheets');
}
 
function use_ie_javascript($js, $options = array()) {
    $javascripts = sfContext::getInstance()->getResponse()->getParameter('javascripts', array(), 'ie');
    if (!is_array($options)) $options = sfToolkit::stringToArray($options);
    $javascripts[] = array($javascript, $options);
    sfContext::getInstance()->getResponse()->setParameter('javascripts', $javascripts, 'ie');
}
 
function include_ie_javascripts() {
    echo get_ie_javascripts();
}
 
function get_ie_javascripts() {
    return get_ie_metas('javascripts');
}
by Nicolas Chambrier on 2007-07-27, tagged css  helper  ie  view 
(3 comments)

Specify another method than toString() to call in object_select_tag

In an object_select_tag, the toString() method is automacally called to display the object in the list.

Sometimes, you can't use the same method for diffrent list on the same objects, so you have to specify wich method to call.

So this is the tip : 'text_method' => 'theMethodToCall'

object_select_tag($my_object, 'myMethod', array (
  'related_class' => 'MyObjectClass',
  'text_method' => 'getCompleteDescription'
))
by whoknows on 2007-07-10, tagged helper  objecthelper 
(1 comment)

Make Your Dynamic Page States Bookmarkable

I've found this to be a pretty good solution for the dynamic page state v. bookmarking conflict that prevents users from bookmarking a specific state of your dynamic page. Typically, if a general user bookmarks your page after spending some time interacting with its dynamic features, she will be disappointed to find her bookmark doesn't reflect the state of the page when she bookmarked it.

If your Javascript is built in a way that you can hijack the window's onLoad handler to initialize a custom state, based what fragment might be in the URI, you just might find this helper function useful.

JavascriptHelper.php

Save this to your project or application's /lib/helper folder.

<?php
 
require_once(sfConfig::get('sf_symfony_lib_dir') . '/helper/JavascriptHelper.php');
 
/**
 * An alternative to the sf default link_to_function.
 * 
 * Adds logic to onclick's concat'd "; return false;" so it only shows up if 
 * the value of the href option doesn't include a #fragment. If no fragment is
 * embedded in the href it is set to "javascript:void(0)" a la Google. 
 * 
 * With the "; return false;" absent, the fragment will show up in the 
 * browser's address bar, and will be included if the user copies the link or 
 * bookmarks the current state of your dynamic page. You can then add a bit of 
 * logic to your window initialization Javascript to detect any fragments in 
 * the URL and adjust the onLoad state accordingly.
 * 
 * @author  Kris Wallsmith <kris [dot] wallsmith [at] gmail [dot] com>
 * @version tested on symfony 1.0.3
 * @see     link_to_function()
 * 
 * @param   string $name
 * @param   string $function
 * @param   mixed $options
 * 
 * @return  string
 */
function my_link_to_function($name, $function, $options = array())
{
    $options = _parse_attributes($options);
 
    $has_href = isset($options['href']);
 
    if(!isset($options['href']))
    {
        $options['href'] = 'javascript:void(0)';
    }
    $options['onclick'] = $function;
 
    if(!$has_href || strpos($options['href'], '#') === false)
    {
        $options['onclick'] .= '; return false;';
    }
 
    return content_tag('a', $name, $options);
}
by Kris Wallsmith on 2007-06-08, tagged helper  javascript 
(2 comments)

Gravatar helper

Gravatar has moved as a plugin with local caching functions see : http://trac.symfony-project.com/trac/wiki/sfGravatarPlugin

by Mickael Kurmann on 2007-06-06, tagged gravatar  helper  imagetag 

Google Analytics / Urchin helper functions

These functions should streamline the process of integrating Google Analytics into your site.

The Code

<?php
 
/**
 * A collection of helper functions for attaching analytics tracking code to
 * links. Used for outbound links and downloads. Functions all produce normal
 * links when analytics is turned off in the configuration.
 * 
 * These functions rely on two configuration variables and the layout code,
 * below. The following variables are required in your app.yml file:
 * 
 * <code>
 *   prod:
 *     analytics:
 *       enabled: on
 *       uacct: UA-XXXXXXX-X
 *   stage:
 *     analytics:
 *       enabled: on
 *       uacct: UA-XXXXXXX-Y
 *   all:
 *     analytics:
 *       enabled: off
 * </code>
 * 
 * Add this code to your layout files for any pages you want to track using
 * Urchin. This could also be done using a filter. Notice you can include a
 * parameter in your response object, 'ut_param', that will be passed to the
 * initial urchinTracker call, if it's present.
 * 
 * <code>
 *     <?php if(sfConfig::get('app_analytics_enabled')): ?>
 *     <script src="https://ssl.google-analytics.com/urchin.js" type="text/javascript"></script>
 *     <script type="text/javascript">
 *     _uacct = "<?php echo sfConfig::get('app_analytics_uacct') ?>";
 *     urchinTracker(<?php if($ut_param = sfContext::getInstance()->getResponse()->getParameter('ut_param')) echo "\"$ut_param\"" ?>);
 *     </script>
 *     <?php endif; ?>
 * </code>
 * 
 * @package     Urchin Helper Functions
 * @subpackage  helper
 * @author      Kris Wallsmith <kris [dot] wallsmith [at] gmail [dot] com>
 * @version     SVN: $Id$
 * @copyright   Have at it ...
 */
 
use_helper('Url', 'Javascript');
 
/**
 * Build a link that includes a call to the Javascript urchinTracker function.
 * 
 * Usually used when linking off your site or to any file on your site that
 * does not include tracking code (i.e. PDF documents, images, etc.)
 * 
 * @see     sfConfig::get('app_analytics_enabled')
 * 
 * @param   (string) $name - name of the link
 * @param   (string) $internal_uri - module/action or @rule
 * @param   (string) $urchin_uri - custom path for urchinTracker
 * @param   (array)  $options - additional HTML parameters
 * 
 * @return  String
 */
function urchin_link_to($name, $internal_uri, $urchin_uri = null, $options = array())
{
    // Only add onClick handler if analytics is enabled.
    if(sfConfig::get('app_analytics_enabled')) 
    {
        // Determine urchin uri.
        if(!$urchin_uri)
        {
            $urchin_uri = url_for($internal_uri);
        }
 
        // Add onClick.
        $new_onclick = 'urchinTracker(\'' . $urchin_uri . '\');';
 
        $options = _parse_attributes($options);
        $options['onclick'] = isset($options['onclick']) ? 
            ($new_onclick . $options['onclick']) :
            $new_onclick;
    }
 
    return link_to($name, $internal_uri, $options);
}
 
/**
 * Build a link to a Javascript call, including a call to urchinTracker.
 * 
 * @see     sfConfig::get('app_analytics_enabled')
 * 
 * @param   (string) $name - name of the link
 * @param   (string) $function - Javascript code
 * @param   (string) $urchin_uri - custom path for urchinTracker
 * @param   (array)  $options - additional HTML parameters
 * 
 * @return  String
 */
function urchin_link_to_function($name, $function, $urchin_uri, $options = array())
{
    // Create link_to_function.
    $link = link_to_function($name, $function, $options);
 
    // Only add onClick handler if analytics is enabled.
    if(sfConfig::get('app_analytics_enabled')) 
    {
        $link = str_replace('onclick="', 'onclick="urchinTracker(\'' . $urchin_uri . '\');', $link);
    }
 
    return $link;
}
 
?>
by Kris Wallsmith on 2007-05-16, tagged analytics  helper  urchin 

Conditional Plural Helper

This one is so simple I hesitated to post it, but it's also so useful.

PluralHelper.php

<?php
 
/**
 * Returns a smart plural of the supplied word.
 * 
 * If there are more than one items in the supplied array, this function will
 * either add an 's' to the end of the supplied word, or return the optional
 * plural version.
 * 
 * @author  Kris Wallsmith <kris [dot] wallsmith [at] gmail [dot] com>
 * 
 * @param   string $text            The word you want returned
 * @param   array $array            The array of items this word is describing
 * @param   string $plural_version  An optional word to return instead of just 
 *                                  adding an 's' (i.e. "people")
 * 
 * @return  String
 */
function pluralize_if_many($text, $array, $plural_version = null)
{
    return count($array) > 1 ? ($plural_version ? $plural_version : ($text . 's')) : $text;
}
 
?>
by Kris Wallsmith on 2007-05-07, tagged helper  inflector  plural  string 
(2 comments)

Augmenting Existing Helper Groups

In previous snippets, I gave examples of new helpers. Some of these helpers could fit into already existing helper groups in the symfony core. I then suggested adding a helper group, like "NumberPlus" to augment the "Number" helper group.

There is a nicer way to do this. To augment the Number helper group without needing to redefine existing functions, add a file in <proj>/apps/<app>/lib/helper/NumberHelper.php and place the following in it:

The Code

<?php
 
include_once('symfony/helper/NumberHelper.php');
 
// custom Number helpers go here
function my_number_helper()
{
  // ...
}
 
// ...

Since symfony's bootstrapping process adds the appropriate paths to the include path of php, this works nicely. Of course, this assumes that your installation of symfony isn't customized too much, so you may need to tweak the path a little bit.

Now, when you need access to predefined symfony Number helpers, or your custom helpers, just use:

Example

<!-- someTemplateSuccess.php -->
<?php use_helper('Number') ?>
 
<?php echo format_number(5) ?>
 
<?php echo my_number_helper() ?>

NOTE: I haven't experimented with this outside of my setup, but since my installation of symfony isn't too unusual, I'm assuming it works nicely. If it doesn't work for you, please post the details so I can modify this snippet if necessary.

by Stephen Riesenberg on 2007-04-18, tagged helper 

sort helper for displaying lists

Developers which are used to the admin generator are used of the functionality how this admin genereator sorts a list.

In the th tag of the list, there is used something like this:

          <?php if ($sf_user->getAttribute('sort', null, 'sf_admin/news/sort') == 'is_published'): ?>
      <?php echo link_to(__('Is published'), 'news/list?sort=is_published&type='.($sf_user->getAttribute('type', 'asc', 'sf_admin/news/sort') == 'asc' ? 'desc' : 'asc')) ?>
      (<?php echo __($sf_user->getAttribute('type', 'asc', 'sf_admin/news/sort')) ?>)
      <?php else: ?>
      <?php echo link_to(__('Is published'), 'news/list?sort=is_published&type=asc') ?>
      <?php endif; ?>

This is pretty hard to read and i have put this stuff of code in a helper method:

/**
 * This helper function does exactly the same code output than
 * ---------
 *           <?php if ($sf_user->getAttribute('sort', null, 'sf_admin/news/sort') == 'is_published'): ?>
 *     <?php echo link_to(__('Is published'), 'news/list?sort=is_published&type='.($sf_user->getAttribute('type',   'asc', 'sf_admin/news/sort') == 'asc' ? 'desc' : 'asc')) ?>
 *     (<?php echo __($sf_user->getAttribute('type', 'asc', 'sf_admin/news/sort')) ?>)
 *     <?php else: ?>
 *     <?php echo link_to(__('Is published'), 'news/list?sort=is_published&type=asc') ?>
 *     <?php endif; ?>
 * ---------
  * 
 * @author Frank Stelzer
 * 
 * @param string $name
 * @param string $base_internal_uri
 * @param string $sort_name
 * @param string $ns the name space
 * @return string html code
 */
function sortable_link_to($name, $base_internal_uri = '', $sort_name = '', $ns = ''){
    $retval = '';
 
    $sort_name = $sort_name=='' ? $name : $sort_name;
 
    $user = sfContext::getInstance()->getUser();
    /*@var $user sfBasicSecurityUser */
 
    if ($user->getAttribute('sort', null, $ns) == $sort_name){
        $retval .= link_to($name, $base_internal_uri.(strpos($base_internal_uri,'?')? '&' : '?'). 'sort='.$sort_name.'&type='.($user->getAttribute('type', 'asc', $ns) == 'asc' ? 'desc' : 'asc'));
        $retval .=show_sort_type($user->getAttribute('type', 'asc', $ns));
    }else {
        $retval = link_to($name, $base_internal_uri.(strpos($base_internal_uri,'?')? '&' : '?'). 'sort='.$sort_name.'&type=asc');
    }
 
    return $retval;
}
 
 
/**
 * helper function which is used in the sortable_link_to function
 *
 * @author Frank Stelzer
 *
 * @param string $type
 * @return string html code
 */
function show_sort_type($type){
    $t=$type=='asc'? '&darr;' : '&uarr;';
    // return " ($t) ";
    return " $t ";
}

You can put in the show_sort_type whatever you want. You can display some arrow images or other stuff....

So, when you have a list/table, which you want to sort, you only have to put this code above in a helper and to write this in a template:

echo sortable_link_to(__('Content'),'module/action'),'content', 'module/action/sort');

This is much better to read than the first code, or? :)

Look in generated action code of the admin generator to see how one has to use an incoming sort request.

These methods are mainly used:

    protected function addSortCriteria($c)
    {
        if ($sort_column = $this->getUser()->getAttribute('sort', null, 'namespace'))
        {
            $sort_column = CustomPeer::translateFieldName($sort_column, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_COLNAME);
            if ($this->getUser()->getAttribute('type', null, 'namespace') == 'asc')
            {
                $c->addAscendingOrderByColumn($sort_column);
            }
            else
            {
                $c->addDescendingOrderByColumn($sort_column);
            }
        }
    }
 
    protected function processSort()
    {
        if ($this->getRequestParameter('sort'))
        {
            $this->getUser()->setAttribute('sort', $this->getRequestParameter('sort'), 'namespace');
            $this->getUser()->setAttribute('type', $this->getRequestParameter('type', 'asc'), 'namespace');
        }
 
        if (!$this->getUser()->getAttribute('sort', null, 'namespace'))
        {
        }
    }

In Your custom action you than need:

        $this->processSort();
 
        $c=new Criteria();
        $this->addSortCriteria($c);
// your other criterias here
by Frank Stelzer on 2007-04-18, tagged helper  list  sort 
(2 comments)

Extra Number Helpers

Here are some extra helpers that I don't think are in symfony anywhere. They would fit rather nicely in the 'Number' helper group.

Example

<!-- someTemplateSuccess.php -->
<?php use_helper('NumberPlus') ?>
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Phone</th>
      <th>Credit Card</th>
      <th>Exp.</th>
      <th>Income</th>
      <th>SSN</th>
    </tr>
  </thead>
  <tbody>
    <?php foreach ($users as $user): ?>
      <tr>
        <td><?php echo $user->getName() ?></td>
        <td><?php echo format_phone($user->getPhone()) ?></td>
        <td><?php echo mask_credit_card($user->getCc()) ?></td>
        <td><?php echo format_exp($user->getExp()) ?></td>
        <td><?php echo format_usd($user->getIncome()) ?></td>
        <td><?php echo format_ssn($user->getSsn()) ?></td>
      </tr>
    <?php endforeach; ?>
  </tbody>
</table>

The Code

<?php
 
/**
 * Formats a number by injecting nonnumeric characters in a specified format
 * into the string in the positions they appear in the format.
 *
 * <strong>Example:</strong>
 * <code>
 *  echo format_string('1234567890', '(000) 000-0000');
 *  // => (123) 456-7890
 *
 *  echo format_string('1234567890', '000.000.0000');
 *  // => 123.456.7890
 * </code>
 *
 * @param string the string to format
 * @param string the format to apply
 * @return the formatted string
 */
function format_string($string, $format)
{
   if ($format == '' || $string == '') return $string;
 
   $result = '';
   $fpos = 0;
   $spos = 0;
   while ((strlen($format) - 1) >= $fpos)
   {
       if (is_alphanumeric(substr($format, $fpos, 1)))
       {
           $result .= substr($string, $spos, 1);
           $spos++;
       }
       else
       {
           $result .= substr($format, $fpos, 1);
       }
 
       $fpos++;
   }
 
   return $result;
}
 
/**
 * Transforms a number by masking characters in a specified mask format,
 * and ignoring characters that should be injected into the string without
 * matching a character from the original string (defaults to space).
 *
 * <strong>Example:</strong>
 * <code>
 *  echo mask_string('1234567812345678', '************0000');
 *  // => ************5678
 *
 *  echo mask_string('1234567812345678', '**** **** **** 0000');
 *  // => **** **** **** 5678
 *
 *  echo mask_string('1234567812345678', '**** - **** - **** - 0000', ' -');
 *  // => **** - **** - **** - 5678
 * </code>
 *
 * @param string the string to transform
 * @param string the mask format
 * @param string a string (defaults to a single space) containing characters to ignore in the format
 * @return string the masked string
 */
function mask_string($string, $format, $ignore = ' ')
{
    if ($format == '' || $string == '') return $string;
 
    $result = '';
    $fpos = 0;
    $spos = 0;
    while ((strlen($format) - 1) >= $fpos)
    {
        if (is_alphanumeric(substr($format, $fpos, 1)))
        {
            $result .= substr($string, $spos, 1);
            $spos++;
        }
        else
        {
            $result .= substr($format, $fpos, 1);
 
            if (strpos($ignore, substr($format, $fpos, 1)) === false) $spos++;
        }
 
        $fpos++;
    }
 
    return $result;
}
 
/**
 * Formats a phone number.
 *
 * @param string the unformatted phone number to format
 * @param string the format to use, defaults to '(000) 000-0000'
 * @return string the formatted string
 *
 * @see format_string
 */
function format_phone($string, $format = '(000) 000-0000')
{
    return format_string($string, $format);
}
 
/**
 * Formats a variable length phone number, using a standard format.
 *
 * <strong>Example:</strong>
 * <code>
 *  echo smart_format_phone('1234567');
 *  // => 123-4567
 *
 *  echo smart_format_phone('1234567890');
 *  // => (123) 456-7890
 *
 *  echo smart_format_phone('91234567890');
 *  // => 9 (123) 456-7890
 *
 *  echo smart_format_phone('123456');
 *  // => 123456
 * </code>
 *
 * @param string the unformatted phone number to format
 *
 * @see format_string
 */
function smart_format_phone($string)
{
    switch (strlen($string))
    {
        case 7:
            return format_string($string, '000-0000');
        case 10:
            return format_string($string, '(000) 000-0000');
        case 11:
            return format_string($string, '0 (000) 000-0000');
        default:
            return $string;
    }
}
 
/**
 * Formats a U.S. Social Security Number.
 *
 * <strong>Example:</strong>
 * <code>
 *  echo format_ssn('123456789');
 *  // => 123-45-6789
 * </code>
 *
 * @param string the unformatted ssn to format
 * @param string the format to use, defaults to '000-00-0000'
 *
 * @see format_string
 */
function format_ssn($string, $format = '000-00-0000')
{
    return format_string($string, $format);
}
 
/**
 * Formats a credit card expiration string. Expects 4-digit string (MMYY).
 *
 * @param string the unformatted expiration string to format
 * @param string the format to use, defaults to '00-00'
 *
 * @see format_string
 */
function format_exp($string, $format = '00-00')
{
    return format_string($string, $format);
}
 
/**
 * Formats (masks) a credit card.
 *
 * @param string the unformatted credit card number to format
 * @param string the format to use, defaults to '**** **** **** 0000'
 *
 * @see mask_string
 */
function mask_credit_card($string, $format = '**** **** **** 0000')
{
    return mask_string($string, $format);
}
 
/**
 * Formats a USD currency value with two decimal places and a dollar sign.
 *
 * @param string the unformatted amount to format
 * @param string the format to use, defaults to '$%0.2f'
 *
 * @see sprintf
 */
function format_usd($money, $dollar = true, $format = '%0.2f')
{
    return ($dollar ? '$' : '') . sprintf($format, $money);
}
 
/**
 * Determines if a string has only alpha/numeric characters.
 *
 * @param string the string to check as alpha/numeric
 *
 * @see is_numeric
 * @see preg_match
 */
function is_alphanumeric($string)
{
    return preg_match('/[0-9a-zA-Z]/', $string);
}

NOTE: Place this in something like myproj/apps/myapp/lib/helper/NumberPlusHelper.php

by Stephen Riesenberg on 2007-03-16, tagged helper  number  template 
(5 comments)

create styled navigation for current view / highlight active link v.2

I have rewritten my original link_to_styled helper to accommodate more common scenarios. This helper will append a class name to the link if the current route matches the one present in the link.

You can specify within the $style_options parameter to match only the module name rather than the entire route. This is useful for top-level navigation links that correspond to your modules.

You can also specify a tag that will encapsulate your link and receive the class name rather than the <a> tag if the active-state requirements are met.

Additionally, any existing class names given to your links are maintained rather than overwritten with the active class name.

As others have suggested, I'm sure it could still be improved to allow for partial route matching, perhaps excluding query parameters.

NOTE: Thanks to Fabian Lange's suggestion, I reordered the parameters so that the style parameters come before the less-used html options.

Enjoy!

in lib/helper/StyleHelper.php:

/**
 * Automatically adds a class name to the link if the link route matches
 * the current module or route.
 *
 * <b>Style Options:</b>
 * - 'class' - the class name to append to the link, defaults to 'current'
 * - 'module_only' - append class name if the current module and link module match
 * - 'tag' - an html to encapsulate the link, which will receive the class name rather than the <a> tag
 *
 * <b>Examples:</b>
 * <code>
 *   echo link_to_styled('Events', 'event/list', 'class=active', 'id=event_link', );
 *   if the current route is 'event/list', then:
 *     => <a href="path/to/event/list/action" id="event_link" class="active">Events</a>
 * 
 *   echo link_to_styled('Events', 'event/list', 'class=active module_only=true tag=li', 'id=event_link');
 *   if the current module matches the module present in the route, then:
 *     => <li class="active"><a href="path/to/event/list/action" id="event_link">Events</a></li>
 * </code>
 *
 * @param  string name of the link, i.e. string to appear between the <a> tags
 * @param  string 'module/action' or '@rule' of the action
 * @param  array additional style options
 * @param  array additional HTML compliant <a> tag parameters
 * @return string XHTML compliant <a href> tag
 * @see    link_to
 */
function link_to_styled($text, $route = '', $style_options = '', $options = '') {
 
    static $context, $current_module_name;
 
    $options = _parse_attributes($options);
    $style_options = _parse_attributes($style_options);
 
    if (!isset($style_options['class']))
    {
        $style_options['class'] = 'current';
    }
 
    if (isset($style_options['module_only']))
  {
        if (!isset($context))
        {
            $context = sfContext::getInstance();
        }
 
        if (!isset($current_module_name))
      {
        $current_module_name = $context->getModuleName();
      }
 
    list($current_route_name, $params) = $context->getController()->convertUrlStringToParameters($route);
        $is_current = $params['module'] == $current_module_name;
  } 
    else 
    {
        $current_route = sfRouting::getInstance()->getCurrentInternalUri();
        $is_current = $current_route == $route;
    }
 
    if (isset($style_options['tag']))
    {
        $tag_options = (true === $is_current) ? array('class'=>$style_options['class']) : array();
        $return_string = content_tag($style_options['tag'], link_to($text, $route, $options), $tag_options);
    }
    else
    {   
        if (true === $is_current)
        {
            $options['class'] = isset($options['class']) ? $options['class'] . ' ' . $style_options['class'] : $style_options['class'];
        }
        $return_string = link_to($text, $route, $options);
    }
 
    return $return_string;
}

and in the template:

<?php use_helper('Style') ?>
<ul id="navlist">
<!-- highlight this link if we are within the "events" module -->
<li><?php echo link_to_styled('My Events', 'events/list', 'module_only=true') ?></li>
</ul>
 
<!-- or you could add the style to the li tag -->
<?php echo link_to_styled('Event List', 'events/list', 'tag=li class=active') ?></li>
by scott meves on 2007-02-24, tagged css  helper  linkto  navigation 
(6 comments)

Complimentary Filters

A common modification to a CRUD module is filtering records on the list page. There are probably several nice ways to do this, including drop-downs, forms, etc. depending on the situation. While some of these approaches are nice, they are often not minimal... We like minimal!

Discussion

Say you have a book table, with a related author and genre. You want to filter the list of books by those related tables with links, paginate your list, and make it sortable via links in the header of the table.

To do this traditionally, you'll need lots of nasty little if statements in your template code, to check for all of these parameters (author_id, genre_id, title, num_pages, page, etc.) and combine them all together into a meaningful uri for your link_to function (something like "book/list?author_id=10&genre_id=5&sort=title&page=5"). We still want that link in the end, but generating it... How about a better way!

Followup Example

<!-- listSuccess.php -->
<?php use_helpers('Filter', 'Pagination') ?>
<h2>Books</h2>
<h3>Filter By</h3>
<table class="filters">
  <tbody>
    <tr>
      <th>Author:</th>
      <td><?php echo filter_navigation(objects_for_filter($authors), 'author_id', $author_id) ?></td>
    </tr>
    <tr>
      <th>Genre:</th>
      <td><?php echo filter_navigation(objects_for_filter($genres), 'genre_id', $genre_id) ?></td>
    </tr>
  </tbody>
</table>
<hr />
<table class="list">
  <thead>
    <tr>
      <th><?php link_to_unless($sort == 'id', 'Id', filter_url('sort', 'id')) ?></th>
      <th><?php link_to_unless($sort == 'title', 'Title', filter_url('sort', 'title')) ?></th>
      <th><?php link_to_unless($sort == 'author_id', 'Author', filter_url('sort', 'author_id')) ?></th>
      <th><?php link_to_unless($sort == 'genre_id', 'Genre', filter_url('sort', 'genre_id')) ?></th>
    </tr>
  </thead>
  <tbody>
    <?php foreach ($pager->getResults() as $book): ?>
      <tr>
        <td><?php echo $book->getId() ?></td>
        <td><?php echo $book->getTitle() ?></td>
        <td><?php echo $book->getAuthor()->getName() ?></td>
        <td><?php echo $book->getGenre()->getName() ?></td>
      </tr>
    <?php endforeach; ?>
  </tbody>
</table>
<?php echo pager_navigation($pager) ?>

NOTE: pager_navigation is covered at http://www.symfony-project.com/snippets/snippet/4, but needs to guess the uri, which is done at http://www.symfony-project.com/snippets/snippet/59.

NOTE: filter_navigation returns an unordered list, so you'll need css to display the <li> tags inline, and remove <ul> padding, margin, etc.

I'll leave it as an exercise to the reader to create the controller (action) code for this template, but it should be obvious.

The Code

<?php
 
/**
 * Generate a url using the current internal uri, but replaces a param with a new value.
 * If the param is not in the current uri's query string, it is added instead.
 *
 * This is useful for a page that uses several filters to record sets,
 * and needs all the filters to work together, instead of blasting each
 * other away when a new link is clicked.
 *
 * <strong>Examples:</strong>
 * <code>
 *  // with current uri => mymodule/myaction?author=10&genre=3
 *
 *  echo link_to('new author', filter_url('author', 5));
 *  // uri when clicked => mymodule/myaction?author=5&genre=3
 *
 *  echo link_to('new genre', filter_url('genre', 1));
 *  // uri when clicked => mymodule/myaction?author=10&genre=1
 *
 *  // with current uri => mymodule/myaction
 *
 *  echo link_to('an author', filter_url('author', 10));
 *  // uri when clicked => mymodule/myaction?author=10
 * </code>
 *
 * @param string the name of the parameter to replace
 * @param string the value to replace the current value with
 * @param boolean use route name
 * @return string the url with the parameter replaced
 * @see link_to
 */
function filter_url($param, $new_value, $with_route_name = false)
{
    // fetch params from query string
    $params = _get_params(_get_query_string());
 
    // replace param with new value
    $params[$param] = $new_value;
 
    return _get_uri($with_route_name) . '?' . _build_query_string($params);
}
 
/**
 * Removes a parameter from the current uri and returns the resulting url.
 *
 * @see filter_url
 */
function remove_filter_url($param, $with_route_name = false)
{
    // fetch params from query string
    $params = _get_params(_get_query_string());
 
    // remove param
    unset($params[$param]);
 
    return _get_uri($with_route_name) . '?' . _build_query_string($params);
}
 
/**
 * Generates an unordered list of links to filter the current record set by.
 * Multiple sets of filter_navigation links will work together, using the current uri.
 *
 * <strong>Examples:</strong>
 * <code>
 *  echo filter_navigation(array(10=>'Jones', 12=>'Smith, J.', 13=>'Darby'), 'author_id', 13);
 *  echo filter_navigation(objects_for_filter($authors), 'author_id', 13);
 * </code>
 *
 * @param array list of key=>value pairs of ids and strings
 * @param string the name of the parameter for this filter
 * @param string the selected id (or null, if none selected)
 * @param string the text to use for the "all" link
 * @see filter_url
 */
function filter_navigation($list, $param, $selected = null, $all_text = 'All')
{
    $html = '';
 
    $html .= content_tag('li', link_to_unless($selected === null, $all_text, remove_filter_url($param)));
    foreach ($list as $key => $value)
    {
        $html .= content_tag('li', link_to_unless($selected == $key, $value, filter_url($param, $key)));
    }
 
    return content_tag('ul', $html);
}
 
/**
 * Generates a simple list from a record set of propel objects.
 * Expects a getId function and a toString function.
 *
 * @param array objects to be converted to a list
 * @see filter_navigation
 */
function objects_for_filter($objects)
{
    $list = array();
 
    foreach ($objects as $object)
    {
        $list[$object->getId()] = $object->toString();
    }
 
    return $list;
}
 
function _get_uri($with_route_name = false)
{
    $internal_uri = sfRouting::getInstance()->getCurrentInternalUri($with_route_name);
    $ar = explode('?', $internal_uri);
 
    return ($with_route_name ? '@' : '') . $ar[0];
}
 
function _get_query_string()
{
    $internal_uri = sfRouting::getInstance()->getCurrentInternalUri();
    $ar = explode('?', $internal_uri);
 
    return isset($ar[1]) ? $ar[1] : '';
}
 
function _get_params($query_string)
{
    // parse query string into associative array
    $params = array();
    if ($query_string != '')
    {
        foreach (explode('&', $query_string) as $kvpair)
        {
            list($key, $value) = explode('=', $kvpair);
            $params[$key] = $value;
        }
    }
 
    return $params;
}
 
function _build_query_string($params)
{
    // build list of key=value strings
    $ar = array();
    foreach ($params as $key => $value)
    {
        $ar[] = $key . '=' . $value;
    }
 
    return implode('&', $ar);
}

NOTE: Place this in apps/myapp/lib/helper/FilterHelper.php.

Additional Usage Examples

<?php echo link_to('new author', filter_url('author_id', 10)) ?>
<?php echo url_for(filter_url('author_id', 10)) ?>
<?php echo link_to_if($condition, 'new author',  filter_url('author_id', 10)) ?>
<?php echo link_to_unless($condition, 'new author',  filter_url('author_id', 10)) ?>
<?php echo button_to('new author',  filter_url('author_id', 10)) ?>

Additional Helpers

<?php
 
/**
 * Shortcut combining link_to and filter_url into single function.
 *
 * @see link_to
 * @see filter_url
 */
function link_to_filter($name, $param, $new_value, $options = array())
{
    return link_to($name, filter_url($param, $new_value), $options);
}
 
/**
 * Shortcut combining url_for and filter_url into single function.
 *
 * @see url_for
 * @see filter_url
 */
function filter_url_for($param, $new_value)
{
    return url_for(filter_url($param, $new_value));
}
 
/**
 * Shortcut combining link_to_if and filter_url into single function.
 *
 * @see link_to_if
 * @see filter_url
 */
function link_to_filter_if($condition, $name, $param, $new_value, $options = array())
{
    return link_to_if($condition, $name, filter_url($param, $new_value), $options);
}
 
/**
 * Shortcut combining link_to_unless and filter_url into single function.
 *
 * @see link_to_unless
 * @see filter_url
 */
function link_to_filter_unless($condition, $name, $param, $new_value, $options = array())
{
    return link_to_unless($condition, $name, filter_url($param, $new_value), $options);
}
 
/**
 * Shortcut combining button_to and filter_url into single function.
 *
 * @see button_to
 * @see filter_url
 */
function button_to_filter($name, $param, $new_value, $options = array())
{
    return button_to($name, filter_url($param, $new_value), $options);
}
by Stephen Riesenberg on 2007-02-03, tagged css  filter  helper  pager  pagination  template  view 

Flash object helper

Simple helper for embeding flash objects using Unobtrusive Flash Objects (UFO). Download and install ufo.js to js/ufo.js and place your movies (*.swf) to web/swf, FlashHelper.php in your lib/helper directory.

In your template

<div id="flashcontent">
  This text is replaced by the Flash movie.
</div>
<?php use_helper('Flash'); ?>
<?php echo flash_object('flashcontent', array(
  'movie'=>'movie.swf', 
  'width'=>'200', 
  'height'=>'300', 
  'bgcolor'=> '#FFFFFF'); 
?>

FlashHelper.php

<?php
 /**
  * Flash object helper - embeds Flash objects (files with the .swf extension)
  * 
  * Uses Unobtrusive Flash Objects (UFO) 
  * 
  * @param string $id - The ID od the div which will be replaced by flash movie
  * @param mixed  $options - Flash object options 
  * @see http://www.bobbyvandersluis.com/ufo/
  */
function flash_object($id, $options = array())
{  
  sfContext::getInstance()->getResponse()->addJavascript('ufo.js');
 
  $options = _parse_attributes($options);
  $absolute = false;
  if (isset($options['absolute']))
  {
    unset($options['absolute']);
    $absolute = true;
  }  
  if(isset($options['size']))
  {
    list($options['width'], $options['height']) = split('x', $options['size'], 2);
    unset($options['size']);
  }
  if(!isset($options['majorversion'])) 
  {
    $options['majorversion'] = 7; 
  }  
  if(!isset($options['build'])) 
  {
    $options['build'] = 0; 
  }  
  // check for all required params  
  foreach(array('movie', 'width', 'height', 'majorversion', 'build') as $required)
  {
    if(!isset($options[$required]))
    {
      throw new sfException("{FlashHelper} Required parameter \"$required\" is missing.");
    }
  }               
  $options['movie'] = flash_path($options['movie'], $absolute);  
  $opts = array();
  foreach ($options as $key => $value)
  {
    $opts[] = $key . ': "' . $value . '"';
  }
  sort($opts);
  // javascript variable name
  $name = $id . '_var';  
  $js = 'var ' . $name . " = {".join(', ', $opts)."};\n";   
  $js .= 'UFO.create('.$name.', "'.$id.'");'."\n";   
  return content_tag('script', "\n//".cdata_section("\n$js\n//")."\n", array('type' => 'text/javascript')); 
}
 
 /**
 * Returns the path to a flash swf movie.
 *
 * <b>Example:</b>
 * <code>
 *  echo flash_path('mymovie');
 *    => /swf/mymovie.swf
 * </code>
 *
 * <b>Note:</b> The asset name can be supplied as a...
 * - full path, like "/swf/movie.swf"
 * - file name, like "movie.swf", that gets expanded to "/swf/movie.swf"
 * - file name without extension, like "movie", that gets expanded to "/swf/movie.css"
 *
 * @param  string asset name
 * @param  bool return absolute path ?
 * @return string file path to the flash movie file
 */
function flash_path($source, $absolute = false)
{
  return _compute_public_path($source, 'swf', 'swf', $absolute);
}
by mishal on 2007-01-26, tagged flash  helper  swf  ufo 
(3 comments)

AJAX Degredation Helper that creates
tags with fragments inside

This helper creates a div tag tag into which links can call other actions and dynamically display its output via AJAX. When JS is not enabled, the links reload the page with GET parameters and allow this helper to load the output.

What do you think of this code? First of all, has it already been done before? And do you think it will actually be useful? How well does it conform to the MVC structure?

Actual code:

<?php
/**
 * This file includes functions that assist in making AJAX degradable
 *
 * These small helper functions add in some of the basic features that allow for degradable
 * JS. The idea is to dynamically load contents when JS is available and let the page reload with
 * appropriate html when JS is not.
 * @author Yining Zhao
 * @package YZ_Helpers
 * @subpackage AJAX_Degredation
 * @since 1/16/2007
 */
 
/**
 * This class creates a pair of <div> tags in which AJAX functions can input dynamic HTML.
 *
 * Usually what happends is that a link is pressed to activate the AJAX command via its onClick method.
 * The HREF attribute is suppressed by a 'return false;' at the end of the onClick method. However
 * when JS is not available, the onClick method is not activated and thus the HREF is called. The 
 * link in the HREF reloads the page and uses GET to pass along the variables to load the designated
 * fragment.
 *
 * If the $trigger_var is 'login_box', here is what the following GET parameters will do:
 *      login_box_display will load the fragment if it is set to 'on', otherwise div style = visibility:none and nothing is loaded
 *          e.g. URL: www.website.com/file.php?login_box_display=on //will load fragment
 *
 *      login_box_make_persistant will let set login_box_display to 'on' in the user session, so as long as
 *          login_box_make_persistant is not set to 'no', the fragment will automatically be loaded on every page 
 *          load regardless of the GET statement
 *          e.g. URL: www.website.com/file.php?login_box_make_persistant=on //will load fragment on every page load
 * 
 * Sample usage of function:
 *      //This function is being called from listSuccess.php template file in my Post module    
 *      div_fragment_creater('login_box','partial','loginPartial',array('referer'=>$referer),array('id'=>'login_div_id'))
 *
 * The source: {@source} 
 *
 * @param string $trigger_var The variable passed via GET that determines if the partial is loaded
 * @param string $fragment_type This tells the function if you are using a partial or a component
 * @param string $fragment_filename The name of the fragment to be called
 * @param array $fragment_values_ary This passes in the values for the fragment
 * @param array $div_attr_ary This contains all the html attributes for the <div> tag
 * @return void Since this function is designed to echo the necessary html, return is void
 */
function div_fragment_creater($trigger_var,$fragment_type, 
                                    $fragment_filename,$fragment_values_ary,$div_attr_ary)
{
    $sf_user=sfContext::getInstance()->getUser()->getAttributeHolder();
    $sf_params=sfContext::getInstance()->getRequest()->getParameterHolder();
    //If in the GET there is a make_persistant command for the given trigger variable, then the display command for the
    //tigger is set for sessions
    if($sf_params->get($trigger_var.'_make_persistant')=='on')
    {
        $sf_user->set($trigger_var.'_display','on');
    } else if($sf_params->get($trigger_var.'_make_persistant')=='off')
    {
        $sf_user->remove($trigger_var.'_display');
        $sf_params->remove($trigger_var.'_display');
    }
    //If the display is set for the trigger in either sf_params or sf_user, display the fragment.
    //Otherwise, don't display the fragment and make the div attribute style = display:none
    $output_fragment=false;
    if(($sf_params->get($trigger_var.'_display')=='on')||($sf_user->get($trigger_var.'_display')=='on'))
    {
        $output_fragment=true;
 
    } else
    {
        $div_attr_arry['style'] = 'display:none';
    }
 
    $div_attr_keys = array_keys($div_attr_ary);
    $div_attr_ary_size = count($div_attr_keys);
    $div_attr='';
    $div_attr_collection='';
    //Cteates the attributes for the <div>
    for($i=0;$i<$div_attr_ary_size;$i++)
    {
        //This creates a string where the div attribute is set to its corresponding value; e.g. id=5
        $div_attr_name = $div_attr_keys[$i];
        $div_attr_collection.=$div_attr_name . ' = "' . $div_attr_ary[$div_attr_name] . '" ';
    }
    echo '<div '.$div_attr_collection.' >';
    if($output_fragment==true)
    {
        switch($fragment_type)
        {
            case 'partial':
                echo include_partial($fragment_filename, $fragment_values_ary);
                break;
            case 'component':
                use_component($fragment_filename, $fragment_values_ary);
                break;
        }
    }
    echo '</div>';
}
?>

Usage Example: In myApp/myModule/list.php:

<body>
Anything is possible...
<?php 
//Creates div with id = myDiv. Usually this div is invisible and empty. But when the get or session variable of triggerVar_display is set to 'on', this div becomes visible and displays the whatsPossiblePartial partial file.
div_fragment_creater('triggerVar','partial','whatsPossiblePartial',array('id'=>$id),array('id'=>'myDiv'))
 
//Provides link to load action into myDiv. If JS is not enabled, link will reload page with triggerVar_display set to 'on'
 
echo link_to_remote('Click To See Whats Possible', array(
            'url'=>'MyModule/WhatPossible?id='.$value,
            'update'=>array('success' => 'myDiv'),
            'loading'=>"Element.show('indicator')",
            'complete'=>visual_effect('blind_down','myDiv',array('duration'=>0.5))';',
            ),          array('href'=>'list?triggerVar_display=on'));
?>
</body>

Inside the whatPossibleSuccess.php:

<html> ...
<?php 
//This file is basically just a wrapper for the whatsPossiblePartial. the reason I have this wrapper is so that I can access it from via link_to_remote() on other pages.
echo include_partial(whatsPossiblePartial,array('id'=>$this->id));
?>
...
</html>
by whoknows on 2007-01-18, tagged ajax  degradation  helper  javascript 

Additional slot helpers

I recently had several template fragments which each had some conditional css that needed to be defined in the template itself. This is a perfect application for slots.

However, since there were several of these fragments/partials being executed, I would have to use several slots. That seemed unnecessary. Consider the following:

Example 1

/* _fragment.php */
<?php append_to_slot('style') ?>
<style>
.class {
  font-size: 12px;
}
</style>
<? end_slot() ?>

Example 2

/* _anotherfragment.php */
<?php append_to_slot('style') ?>
<style>
  .anotherclass {
    <?php if ($bool): ?>
      font-size: 24px;
    <?php else: ?>
      font-size: 26px;
    <?php endif; ?>
  }
</style>
<?php end_slot() ?>

Those were just an example, so don't laugh. They do illustrate, however, the use of a helper which continually appends your content to the existing slot, which may or may not already contain content; in this case the "style" slot.

Then of course, you can use the slot in your layout.php (in the head tag):

...
<head>
...
<?php if (has_slot('style')) echo get_slot('style') ?>
</head>

Note: while the append_to_slot function can be ended by either end_slot() or end_append_to_slot(), the prepend_to_slot function must be ended by end_prepend_to_slot()!

The code

<?php
 
/**
 * Begins the capturing of a slot,
 * but echoes the current value of a slot with the same name,
 * if one exists, before capturing the rest of the slot content.
 *
 * <strong>Example</strong>
 * <code>
 *  // first myslot
 *  slot('myslot');
 *  echo 'something to put in "myslot"';
 *  end_slot();
 *  // second myslot
 *  append_to_slot('myslot');
 *  echo 'some stuff to append to existing "myslot" slot';
 *  end_slot();
 * </code>
 *
 * @param   string slot name
 * @return  void
 * @see     end_append_to_slot, slot, end_slot
 */
function append_to_slot($name)
{
    // check for the existence of a slot with the same name
    $content = '';
    if (has_slot($name)) $content = get_slot($name);
 
    // regardless, begin capturing the slot
    slot($name);
 
    // echo either a blank string, or the previous value of the slot with the same name
    echo $content;
}
 
/**
 * Stops the content capture and save the content in the slot.
 * This function is interchangeable with end_slot, and is not necessary.
 * It is useful only for consistency's sake.
 *
 * @return  void
 * @see     append_to_slot, slot, end_slot
 */
function end_append_to_slot()
{
    // simply end the current slot
    end_slot();
}
 
/**
 * Begins the capturing of a slot,
 * but saves the current value of a slot with the same name,
 * if one exists, and then proceeds to capture the slot content.
 *
 * @param   string slot name
 * @return  void
 * @see     end_prepend_to_slot, slot, end_slot
 */
function prepend_to_slot($name)
{
    $context = sfContext::getInstance();
    $response = $context->getResponse();
 
    // check for the existence of a slot with the same name
    $content = '';
    if (has_slot($name)) $content = get_slot($name);
 
    // save the current slot value in a temporary location
    $response->setParameter('old_slot', $content, 'symfony/view/sfView/slot');
 
    // begin capturing the slot
    slot($name);
}
 
/**
 * Retrieves the old value of a slot which was backed up in prepend_to_slot,
 * if one exists, and echoes this value,
 * and then proceeds to stop the content capture and save the content in the slot.
 *
 * @return  void
 * @see     prepend_to_slot, slot, end_slot
 */
function end_prepend_to_slot()
{
    $context = sfContext::getInstance();
    $response = $context->getResponse();
 
    // retrieve the old value for this slot
    $content = $response->getParameter('old_slot', '', 'symfony/view/sfView/slot');
    // remove the old value
    $response->getParameterHolder()->remove('old_slot', 'symfony/view/sfView/slot');
 
    // echo the old value of the slot, or a blank string if no slot existed
    echo $content;
 
    // end the current slot
    end_slot();
}

Remember: you can place this in myproject/apps/myapp/lib/helper/PartialPlusHelper.php

Then you can use it with <?php use_helper('PartialPlus') ?>

by Stephen Riesenberg on 2006-12-16, tagged helper  template  view 

Template fragments, cont.

I'm not sure how useful these will be, but I came up with the idea, so I figured I'd post them as well.

Example

<table>
  <?php foreach ($vars as $key => $value): ?>
    <tr>
      <th><?php include_fragment('label', array('id' => $key, 'object' => $value))) ?></th>
      <td><?php include_fragment('input', array('id' => $key, 'object' => $value))) ?></td>
    </tr>
  <?php endforeach; ?>
</table>

These helpers use the fragment idea from http://www.symfony-project.com/snippets/snippet/124 and take it a step further. There's a difference here, however, that's important to note:

Note: Unlike partials, these fragments will have absolutely no variables available, unless explicitly passed in. Templating variables provided by symfony will not be available, due to php's scope handling when inside of functions. That's why my previous snippet used include(fragment_name('myfragment')) instead, which gave the fragment access to all currently in-scope variables.

The code

<?php
 
/**
 * Includes a template fragment.
 * This function is similar in nature to include_partial,
 * but does not use sfView, and therefore has virtually no overhead.
 * 
 * <strong>Example</strong>
 * <code>
 *  include_fragment('mypartial');
 * </code>
 * 
 * @param   string fragment name
 * @return  void
 * @see     include_partial, get_fragment, fragment_path, fragment_name
 */
function include_fragment($templateName, $vars = array())
{
    echo get_fragment($templateName, $vars);
}
 
/**
 * Evaluates and returns a template fragment.
 * The syntax is similar to that of include_fragment.
 * 
 * <strong>Example</strong>
 * <code>
 *  echo get_fragment('mypartial');
 * </code>
 * 
 * @param   string fragment name
 * @return  string result of a fragment include
 * @see     include_partial, include_fragment, fragment_path, fragment_name
 */
function get_fragment($templateName, $vars = array())
{
    // start the output buffer
    ob_start();
    ob_implicit_flush(0);
    // extract the variables in the scope of this function
    extract($vars);
    // include the file
    include(fragment_path($templateName));
    // clear the output buffer, and return the content
    return ob_get_clean();
}

Remember: you can place this in myproject/apps/myapp/lib/helper/PartialPlusHelper.php

Then you can use it with <?php use_helper('PartialPlus') ?>

by Stephen Riesenberg on 2006-12-16, tagged helper  template  view 

Template fragments - when even partials are too much

For the record, partials are wonderful. Self-sustained pieces of gui code, with access to all the symfony templating tools you need.

Sometimes however, even partials tend to be overkill. And sometimes, too, you have an annoying little piece of gui code that ought to go into your main template (indexSuccess.php or what have you), but you can't bear to put it there, because it would disturb the flow of an otherwise beautifully crafted template.

The truth is, every reuseable piece of gui code that isn't a full blown template really fits the partial paradigm, but sometimes it's just too much overhead, too much trouble, or just not necessary. Consider the following:

Example 1

<table>
  <?php foreach ($vars as $key => $value): ?>
    <tr>
      <th><?php include(fragment_name('label')) ?></th>
      <td><?php include(fragment_name('input')) ?></td>
    </tr>
  <?php endforeach; ?>
</table>

Example 2

<table>
  <?php foreach ($vars as $key => $value): ?>
    <tr>
      <th><?php include(fragment_path('somemodule/label')) ?></th>
      <td><?php include(fragment_path('anothermodule/input')) ?></td>
    </tr>
  <?php endforeach; ?>
</table>

The _label.php file could contain a few if statements... enough to generate the label for our input. It would look quite a bit uglier to place the code right in the th tag, but that's what we're really going for. _label.php would immediately have access to $key and $value, and any other variables already in scope, including typical templating variables provided by symfony. Additionally, there's no overhead to deal with, which helps if that foreach loop will execute 200 times (well, maybe that's a stretch). Finally, it just looks symfony-esque (at least to me)!

The code

<?php
 
/**
 * Get the path to a fragment.
 * The syntax is similar to that of include_partial.
 * To be used in conjunction with php's include function.
 * Variables in scope in the caller will be available in the execution of the fragment.
 * 
 * <strong>Example</strong>
 * <code>
 *  include(fragment_path('mypartial'))
 * </code>
 * 
 * <strong>Example</strong>
 * <code>
 *  include(fragment_path('mymodule/mypartial'))
 * </code>
 * 
 * @param   string fragment name
 * @return  string path to the fragment
 * @see     get_fragment, include_fragment, include_partial
 */
function fragment_path($templateName)
{
    // partial is in another module?
    if (false !== ($sep = strpos($templateName, '/')))
    {
        $moduleName   = substr($templateName, 0, $sep);
        $templateName = substr($templateName, $sep + 1);
    }
    else
    {
        $moduleName = sfContext::getInstance()->getActionStack()->getLastEntry()->getModuleName();
    }
    $fragmentName = fragment_name($templateName);
    // test for global fragment
    if ($moduleName === 'global')
    {
        $path = sfConfig::get('sf_app_template_dir');
    }
    // if not global, or the global fragment doesn't exist
    if ($moduleName !== 'global' || !is_readable($path.'/'.$fragmentName))
    {
        // get the full path to the file
        $path = sfConfig::get('sf_app_module_dir').'/'.$moduleName.'/'.sfConfig::get('sf_app_module_template_dir_name');
    }
    // return the full path to the partial
    return $path.'/'.$fragmentName;
}
 
/**
 * Translates a nice fragment name into the filename adhering to the naming convention for partials.
 * 
 * <strong>Example</strong>
 * <code>
 *  include(fragment_name('mypartial'))
 * </code>
 * 
 * @param   string fragment name
 * @return  string fragment filename
 * @see     fragment_path
 */
function fragment_name($templateName)
{
    return '_'.$templateName.'.php';
}

Remember: you can place this in myproject/apps/myapp/lib/helper/PartialPlusHelper.php

Then you can use it with <?php use_helper('PartialPlus') ?>

by Stephen Riesenberg on 2006-12-16, tagged helper  template  view 
(1 comment)

baseurl_for with an external application

Here is a usefull helper to calculate url for another application :

function baseurl_for($application, $absolute = false)
{
    $url = $absolute ? 'http://' . $_SERVER["HTTP_HOST"] : '';
    $url .= '/' . $application . (SF_ENVIRONMENT != 'prod' ? '_' . SF_ENVIRONMENT : '') . '.php/';
    return $url;
}

in the action :

<?php echo link_to('Whatever', baseurl_for('application', true) . 'module/action') ?>

I really feel like this feature is missing, hope this could added soon ;) ... example corrected (but sadly not so usefull) according to francois's comment.

by brikou on 2006-11-24, tagged application  base  helper  url 
(4 comments)

One link, multiple ajax div updates, only one remote call

The problem seems to arise quite frequently: a page contains one Ajax link, but the remote function must update several divs on the page. Consider, for instance, the following template:

<h1>First zone to update</h1>
<div id="first_zone">
  Hello there
</div>
 
<h1>Second zone to update</h1>
<div id="second_zone">
  <p>How do you do, <strong>mate</strong>?
</div>
 
<h1>Ajax link</h1>
<?php echo use_helper('Javascript') ?>
<?php echo link_to_remote('click me', array(
  'url'    => 'test/ajax',
  'update' => 'result',
  'script' => true,
)) ?>
<div id="result">
</div>

What would the test/ajax action look like to update both the first_zone and the second_zone?

For the code of the action itself (executeAjax()), we'll ignore it since it really depends on what logic you put in your Ajax interaction. For this example, it will be empty.

The code of the template (ajaxSuccess.php) can be as follows:

<?php echo use_helper('Javascript') ?>
 
<?php slot('first_update') ?>
  So you like clicking, uh?
<?php end_slot() ?>
 
<?php slot('second_update') ?>
  <p>I'd like to test quotes (like "). </p>
  <p>And <strong>tags</strong>, too.</p>
<?php end_slot() ?>
 
<?php echo javascript_tag(
 
  update_element_function('first_zone', array(
    'content' => get_slot('first_update'),
  ))
  .
  update_element_function('second_zone', array(
    'content' => get_slot('second_update'),
  )) 
 
) ?>
 

Once rendered, the HTML code sent to the user will look like this:

<script type="text/javascript">
//<![CDATA[
$('first_zone').innerHTML = '  So you like clicking, uh?\n';
$('second_zone').innerHTML = '  <p>I\'d like to test quotes (like \"). </p>\n  <p>And <strong>tags</strong>, too.</p>\n';
//]]>
</script>

And this will do exactly what we wanted: update both zones with different content, in a single remote call. The interest of using the slot helpers is that you don't need to worry about escaping the content passed to the JavaScript function, and it looks really nice in your favorite syntax-highlighting text editor.

If you happen to do this a lot, maybe you will want to package the multiple updater into a helper. That's quite easy:

function update_elements_function($updates)
{
  $res = "";
  foreach($updates as $zoneName => $slotName)
  {
    $res .= update_element_function($zoneName, array('content' => get_slot($slotName)));
  }
  return javascript_tag($res);
}

Then you could replace the call to the javascript_tag() in the ajaxSuccess.php template by a simpler:

<?php echo update_elements_function(array(
  'first_zone'  => 'first_update',
  'second_zone' => 'second_update',
)) ?>

Performancewise, if the Ajax response is small (below 512 Bytes), you'd better use the JSON approach.

by Francois Zaninotto on 2006-11-22, tagged ajax  helper  multiple 
(3 comments)

More on multiple ajax div updates

Quentin Garnier, in this snippet: http://www.symfony-project.com/snippets/snippet/57, suggested a way to have a link call multiple remote functions (with "in order" calls being enforced by a new 'wait' parameter). With a slightly modified version of his code, here are a few more functions to be added to you Javascript.php function in the symfony core:

function periodically_call_remotes($options = array())
{
  $frequency = isset($options[0]['frequency']) ? $options[0]['frequency'] : 10; // every ten seconds by default
  $code = 'new PeriodicalExecuter(function() {'.remote_functions($options).'}, '.$frequency.')';
 
  return javascript_tag($code);
}
 
function link_to_remotes($name, $options = array(), $html_options = array())
{
  return link_to_function($name, remote_functions($options), $html_options);
}
 
function remote_functions($options)
{
  // Multi ajax call
  $multi_function = "";
  // First pass (wait)
  for ($i = 0; isset($options[$i]); $i++) {
    if (isset($options[$i]['wait']) && $options[$i]['wait'] != $i && isset($options[$options[$i]['wait']])) {
      if (isset($options[$options[$i]['wait']]['complete'])) {
          $options[$options[$i]['wait']]['complete'] .= '; ' . remote_function($options[$i]);
      } else {
          $options[$options[$i]['wait']]['complete'] = remote_function($options[$i]);
      }
    }
  }
 
  // Second pass
  for ($i = 0; isset($options[$i]); $i++) {
    if (!(isset($options[$i]['wait']) && $options[$i]['wait'] != $i && isset($options[$options[$i]['wait']]))) {
      $multi_function .=  remote_function($options[$i]) . ';';
    }
  }
  return $multi_function;
}

To use it, you could do something like this:

<?php echo link_to_remotes('multi ajax',
    array(
        array(
            'update' => 'first_zone',
            'url' => '/module/action1',
            'loading' => "Element.show('indicator')"
        ),
        array(
            'update' => 'second_zone',
            'url' => '/module/action2',
            'wait' => 0 /* update in a second server call, after 'news_zone' returns */
        ),
        array(
            'update' => 'third_zone',
            'url' => '/module/action3',
            'wait' => 0
        ),
        array(
            'update' => 'fourth_zone',
            'url' => '/module/action4',
            'complete' => "Element.hide('indicator')",
            'wait' => 2 /* update in a third server call, after 'third_zone' returns */
        )
    )
) ?>

or this:

<?php echo periodically_call_remotes('multi ajax',
    array(
        array(
            'frequency' => 60 /* every 60 seconds, frequency only needs to be specified in first call, not subsequent calls */
            'update' => 'first_zone',
            'url' => '/module/action1',
            'loading' => "Element.show('indicator')"
        ),
        array(
            'update' => 'second_zone',
            'url' => '/module/action2',
            'wait' => 0 /* update in a second server call, after 'news_zone' returns */
        ),
        array(
            'update' => 'third_zone',
            'url' => '/module/action3',
            'wait' => 0
        ),
        array(
            'update' => 'fourth_zone',
            'url' => '/module/action4',
            'complete' => "Element.hide('indicator')",
            'wait' => 2 /* update in a third server call, after 'third_zone' returns */
        )
    )
) ?>

Notes

It should be noted that using the 'wait' parameter will cause all top level ajax remote function calls to be executed in one call to the server, and each second level to be executed in a second call, etc. Basically, multiple calls to the server are generated by the user action.

This will cause huge performance issues if you attempt to use these functions in a high-traffic scalable-sensitive system. It is mainly intended for a link which executes two or three functions at most. To do more, you should consider using another approach, like the JSON approach documented in the wiki.

by Stephen Riesenberg on 2006-11-20, tagged ajax  helper  multiple 
(2 comments)

create styled navigation for current view

Often times in my navigation I want to change the style of the "current" tab, link, menu item, etc. to reflect that the user is in fact on that page. In order to do this I created a super simple helper called link_to_styled. I added this code to app/lib/helper/StyleHelper.php:

function link_to_styled($text, $route = '', $options = '', $class_name = 'current') {
    $current_route = sfRouting::getInstance()->getCurrentInternalUri();
    if($route == $current_route) {
        if(is_array($options)) {
            $options['class'] = $class_name;
        } else {
            $options .= ' class='.$class_name;
        }
    }
    return link_to($text, $route, $options);
}

and then in my global navigation template I do something like this:

<?php use_helper('Style') ?>
<ul id="navlist">
<li><?php echo link_to_styled('My Friends', 'friends/list') ?></li>
<li><?php echo link_to_styled('My Clubs', 'clubs/list') ?></li>
<li><?php echo link_to_styled('My Events', 'events/list')?></li>
</ul>

The helper will automatically add a class='current' to the link tag. This function could be made more robust, but for a quick and simple solution it seems to do the trick.

by scott meves on 2006-11-02, tagged css  helper  navigation 
(2 comments)

ColorPicker Helper

Here is a code for new helper functions : input_color_tag and object_input_color_tag which display a input field. When the focus is on the input a color-picker appear.

See the demo on the bottom of this webpage

2 colorpickers are available by switching the button on the right : a small and simple one, a bigger one like photoshop color picker.


To work, the colorpicker need :

You have to specify in your template or yml config file you want to use this helper php file.


Files are available here (zip files)

by Sylvain PAPET on 2006-10-19, tagged colorpicker  custom  helper 
(7 comments)

use helpers from an action

technically this breaks MVC separation. but it can be handy (for formating data being stored to a database from an action, for example. This is most easily accomplished by copying the use_helper code to a custom class.

create a custom class in project\lib (or app\lib). i'll use Misc.class.php.

<?php
 
class Misc
{
    public static function use_helper($helperName)
    {
      static $loaded = array();
      if (isset($loaded[$helperName]))
      {
        return;
      }
 
      if (is_readable(sfConfig::get('sf_symfony_lib_dir').'/helper/'.$helperName.'Helper.php'))
      {
        // global helper
        include_once(sfConfig::get('sf_symfony_lib_dir').'/helper/'.$helperName.'Helper.php');
      }
      else if (is_readable(sfConfig::get('sf_app_module_dir').'/'.sfContext::getInstance()->getModuleName().'/'.sfConfig::get('sf_app_module_lib_dir_name').'/helper/'.$helperName.'Helper.php'))
      {
        // current module helper
        include_once(sfConfig::get('sf_app_module_dir').'/'.sfContext::getInstance()->getModuleName().'/'.sfConfig::get('sf_app_module_lib_dir_name').'/helper/'.$helperName.'Helper.php');
      }
      else
      {
        // helper in include_path
        include_once('helper/'.$helperName.'Helper.php');
      }
      $loaded[$helperName] = true;
    }
}

(clear your cache after adding a custom class, of course).

now to use a helper from an action:

Misc::use_helper('Date');
....
by hansbricks on 2006-08-02, tagged action  helper 
(4 comments)

Rich select_date_tag

select_date_tag is not available in 'rich' version. Waiting for a real enhancement, this is my workaround.

This is the RichDateHelper.php file:

<?php
 
use_helper('Form');
 
function rich_select_date_tag($name, $value = null, $options = array(), $html_options = array()) {
 
    $context = sfContext::getInstance();
  if (isset($options['culture']))
  {
    $culture = $options['culture'];
    unset($options['culture']);
  }
  else
  {
    $culture = $context->getUser()->getCulture();
  }
 
    // register our javascripts and stylesheets
  $langFile = '/sf/js/calendar/lang/calendar-'.strtolower(substr($culture, 0, 2));
  $jss = array(
    '/sf/js/calendar/calendar',
    is_readable(sfConfig::get('sf_symfony_data_dir').'/web/'.$langFile.'.js') ? $langFile : '/sf/js/calendar/lang/calendar-en',
    '/sf/js/calendar/calendar-setup',
  );
  foreach ($jss as $js)
  {
    $context->getResponse()->addJavascript($js);
  }
 
  $js = '
        function updateSelect(cal) {
            var date = cal.date;
            var selectMonth = document.getElementById("'.get_id_from_name($name).'_month");
            selectMonth.selectedIndex = date.getMonth();
            var selectDay = document.getElementById("'.get_id_from_name($name).'_day");
            selectDay.selectedIndex = (date.getDate() - 1);
            var selectYear = document.getElementById("'.get_id_from_name($name).'_year");
            selectYear.selectedIndex = (date.getFullYear() - '.$options['year_start'].');
        }
    document.getElementById("trigger_'.$name.'").disabled = false;
    Calendar.setup({
            inputField : "'.$name.'_rich_sel_date",
            ifFormat : "%Y-%m-%d",
            button : "trigger_'.$name.'",
            singleClick : true,
            onUpdate : updateSelect,
            showsTime : false,
            range : ['.$options['year_start'].', '.$options['year_end'].'],
            showOthers : false,
            cache : 1,
            weekNumbers : false,
            firstDay : 1
    });
  ';
 
    $html = select_date_tag($name, $value, $options, $html_options);
 
  // calendar button
  $calendar_button = '...';
  $calendar_button_type = 'txt';
  if (isset($options['calendar_button_img']))
  {
    $calendar_button = $options['calendar_button_img'];
    $calendar_button_type = 'img';
    unset($options['calendar_button_img']);
  }
  else if (isset($options['calendar_button_txt']))
  {
    $calendar_button = $options['calendar_button_txt'];
    $calendar_button_type = 'txt';
    unset($options['calendar_button_txt']);
  }
 
  if ($calendar_button_type == 'img')
  {
    $html .= image_tag($calendar_button, array('id' => 'trigger_'.$name, 'style' => 'cursor: pointer'));
  }
  else
  {
    $html .= content_tag('button', $calendar_button, array('type' => 'button', 'disabled' => 'disabled', 'onclick' => 'return false', 'id' => 'trigger_'.$name));
  }
 
  // add javascript
  $html .= content_tag('script', $js, array('type' => 'text/javascript'));
  $html .= '<div id="'.$name.'_rich_sel_date" style="display: inline;"></div>';
 
  return $html;
}
by Jacopo Romei on 2006-07-31, tagged date  forms  helper  rich  select 
(2 comments)

Helper for Javascript Tabbed Panes

I figured I'd post this little helper for using the tabbed pane JS from http://webfx.eae.net/dhtml/tabpane/tabpane.html in your templates.

Simply download the JS package from the site above and place it in your web/js folder. I chose to move the css files from the JS folder to my CSS folder, but you can do whatever you like.

tabsHelper.php

<?php
/**
 * Helper for Javascript Tabbed Panes
 * 
 * Example Usage
 * <code>
 *  <?php use_helper('tabs') ?>
 *  <?php tabMainJS("tp1","tabPane1", "tabPage1", 'General');?>
 *            This is text of tab 1. This is text of tab 1. This is text of tab 1. 
 *      <?php tabPageOpenClose("tp1", "tabPage2", 'Security');?>
 *            This is text of tab 2. This is text of tab 2. This is text of tab 2. 
 *  <?php tabInit();?>
 * </code>
 * 
 * @package    Helpers
 * @author     Jason Ibele
 * @version    SVN: $Id: tabsHelper.php 4 2006-07-19 14:00:47Z jason.ibele $
 */
 
$response = sfContext::getInstance()->getResponse();
$response->addJavascript('tab/tabpane.js');
$response->addStylesheet('tab/tab.webfx.css');
 
/**
 * Opens a new TabPane object and creates first tab
 *
 * @param string $mid         JavaScript variable name to use for webFXTabPane object
 * @param string $id          Main container div ID
 * @param string $page_id     Name for div ID, each needs to be unique
 * @param string $H2_title    The title for the tab
 * @param string $main_class  Optional class name for main Div (note this must match original class definitions)
 * @param string $page_class  Optional class name for page Div (note this must match original class definitions)
 */
function tabMainJS($mid, $id, $page_id, $H2_title, $main_class='tab-pane', $page_class='tab-page')
{
  echo '<div class="'.$main_class.'" id="'.$id.'">'."\n\t";
  echo '<script type="text/javascript">'."\n\t";
  echo $mid.' = new WebFXTabPane( document.getElementById( "'.$id.'" ) );'."\n\t";
  echo '</script>'."\n\t";
  echo '<div class="'.$page_class.'" id="'.$page_id.'">'."\n\t";
  echo '<h2 class="tab">'.$H2_title.'</h2>'."\n\t";
  echo '<script type="text/javascript">'.$mid.'.addTabPage( document.getElementById( "'.$page_id.'" ) );</script>'."\n";
}
 
/**
 * Closes and existing pane div and opens a new one with required JS
 *
 * @param string $mid         JavaScript variable to use for webFXTabPane object
 * @param string $page_id     Name for div ID, each needs to be unique
 * @param string $H2_title    The title for the tab
 * @param string $page_class  Optional class name for page Div (note this must match original class definitions)
 */
function tabPageOpenClose($mid, $page_id, $H2_title, $page_class='tab-page')
{
  echo '</div>'."\n\t";
  echo '<div class="'.$page_class.'" id="'.$page_id.'">'."\n\t";
  echo '<h2 class="tab">'.$H2_title.'</h2>'."\n\t";
  echo '<script type="text/javascript">'.$mid.'.addTabPage( document.getElementById( "'.$page_id.'" ) );</script>'."\n";
}
 
/**
 * Initiates the javascript for tabs and closes the remaining divs
 *
 * @param string $mid    JavaScript variable to use for webFXTabPane object
 * @param string $n      selected index of tab you want to force as set
 */
function tabInit($mid='', $n='')
{
  echo "\t".'</div>'."\n\t";
  echo '<script type="text/javascript">'."\n\t";
  echo 'setupAllTabs();'."\n\t";
 
  if($n){ // n = selected index of tab you want to force as set
    echo $mid.'.setSelectedIndex('.$n.');';
  }
 
  echo '</script>'."\n";
  echo '</div>'."\n";
}
 
?>

template

<?php use_helper('tabs') ?>
 
 
 
 
 
<!-- open the first tab -->
<?php tabMainJS("tp1","tabPane1", "tabPage1", 'General');?><!-- General is the name of the tab -->
 
          This is text of tab 1. This is text of tab 1. This is text of tab 1. 
 
 
<!-- second tab -->     
    <?php tabPageOpenClose("tp1", "tabPage2", 'Security');?> <!-- Security is the name of the tab -->
 
          This is text of tab 2. This is text of tab 2. This is text of tab 2. 
 
 
<!-- third tab -->      
    <?php tabPageOpenClose("tp1", "tabPage3", 'Example');?> <!-- Example is the name of the tab -->
 
          This is text of tab 3. This is text of tab 3. This is text of tab 3.
 
 
<!-- close tabs and initiate the JS -->
<?php tabInit();?>
by Jason Ibele on 2006-07-31, tagged helper  javascript  tabs 
(1 comment)

call the translation helper from an action

You sometimes need to access the __() helper for text translation outside of a template. There are two ways to do it.

You can include the I18NHelper.php manually in your action, or use the symfony command to include a helper (sfLoaded::LoadHElpers()):

sfLoader::LoadHelpers(array('I18N'));

The problem is that this will add the helper functions to the global namespace. Alternately, use the following syntax:

sfContext::getInstance()->getI18n()->__($text, $args, 'messages');

Note: prior to 1.0, you had to do this (now deprecated)

sfConfig::get('sf_i18n_instance')->__($text, $args, 'messages');

(updated 29/11/06)

by Francois Zaninotto on 2006-07-24, tagged helper  i18n 
(3 comments)

Encode URL and caption like Smarty does ;)

This is very simple and effective mail helper. Original code is taken from Smarty's mailto plugin.
This helper scrambles your email addresses so those nasty web bots 'can not' harvest them.

In /lib/helper/MailEncodeHelper.php

<?php
function encode_mail($Address, $Caption=null)
{
  if ($Caption == null)
    $Caption = $Address;
 
  $string = 'document.write(\'<a href="mailto:'.$Address.'">'.$Caption.'</a>\');';
  $js_encode = '';
  for ($x=0; $x < strlen($string); $x++)
    $js_encode .= '%' . bin2hex($string[$x]);
 
  return '<script type="text/javascript">eval(unescape(\''.$js_encode.'\'))</script>';
}
?>

In template

<?php use_helper('MailEncode') ?>
 
My email: <?php echo encode_mail('info@example.com'); ?>  
or   
My email: <?php echo encode_mail('info@example.com', 'Damjan Malis'); ?>
by Damjan Malis on 2006-07-19, tagged email  encode  helper 
(4 comments)

Minimal CSS pagination helper

This is helper is mostly inspired by Pagination navigation helper, but there are some differences:

PaginationHelper.php

function pagination($pager)
{
    $uri = sfRouting :: getInstance()->getCurrentInternalUri();
    $html = '';
 
    if ($pager->haveToPaginate())
    {
        $uri .= strstr($uri, '?') ? '&page=' : '?page=';
 
        if ($pager->getPage() != 1)
        {
            $html .= '<li>' . link_to('first', $uri . '1') . '</li>';
            $html .= '<li>' . link_to('previous', $uri . $pager->getPreviousPage()) . '</li>';
        }
 
        foreach ($pager->getLinks() as $page)
        {
            if ($page == $pager->getPage())
                $html .= '<li><strong>' . link_to($page, $uri . $page) . '</strong></li>';
            else
                $html .= '<li>' . link_to($page, $uri . $page) . '</li>';
        }
 
        if ($pager->getPage() != $pager->getLastPage())
        {
            $html .= '<li>' . link_to('next', $uri . $pager->getNextPage()) . '</li>';
            $html .= '<li>' . link_to('last', $uri . $pager->getLastPage()) . '</li>';
        }
 
        $html = '<ul class="pagination">' . $html . '</ul>';
    }
 
    return $html;
}

Minimal CSS

ul.pagination li {
    display: inline;
    list-style-type: none;
    padding-right: 1em;
}

In your template

<?php echo use_helper('Pagination') ?>
<?php echo pagination($pager) ?>
by brikou on 2006-07-19, tagged css  helper  pager  pagination  propel 
(6 comments)

One link, multiple ajax div updates

The problem

I have:

<?php
 
echo link_to_remote("update news",
                     array(
                           'update'  => 'news_zone',
                           'url'     => 'news/index',
                          )
                   )
?>

The div with the id 'news_zone' will be updated if i click on the link 'update news'. But how can i update another div ?

My solution

You have to add the code in the file symfony/helper/JavascriptHelper.php:

function remote_function($options)
  {
    sfContext::getInstance()->getResponse()->addJavascript('/sf/js/prototype/prototype');
 
    // Multi ajax call
    if (isset($options[0]) && is_array($options[0])) {
        $multi_function = "";
        // First pass (wait)
        for ($i = 0; isset($options[$i]); $i++) {
                if (isset($options[$i]['wait']) && $options[$i]['wait'] != $i && isset($options[$options[$i]['wait']])) {
                        if (isset($options[$options[$i]['wait']]['complete'])) {
                                $options[$options[$i]['wait']]['complete'] .= '; ' . remote_function($options[$i]);
                        } else {
                                $options[$options[$i]['wait']]['complete'] = remote_function($options[$i]);
                        }
                }
        }
 
        // Second pass
        for ($i = 0; isset($options[$i]); $i++) {
                if (!(isset($options[$i]['wait']) && $options[$i]['wait'] != $i && isset($options[$options[$i]['wait']]))) {
                        $multi_function .=  remote_function($options[$i]) . ';';
                }
        }
        return $multi_function;
    }
 
    $javascript_options = _options_for_ajax($options);
...

Now you can use a new syntax (This patch won't affect the "old" syntax. You can use it):

<?php
 
echo link_to_remote('multi ajax', array(
                                         array('update' => 'news_zone',
                                               'url' => '/news/index'
                                              ),
                                         array('update' => 'second_zone',
                                               'url' => '/module/action'
                                               )
                                       )
                   );
 
?>

I provide a special feature: "ajax in order". Requests are asynchronous. Now, you can set an order:

<?php
 
echo link_to_remote('multi ajax', array(
                                         array('update' => 'news_zone',
                                               'url' => '/news/index'
                                              ),
                                         array('update' => 'second_zone',
                                               'url' => '/module/action',
                                               'wait' => 0 /* index table. So update after 'news_zone' */
                                               )
                                       )
                   );
 
?>
by Quentin Garnier on 2006-07-18, tagged ajax  helper  multiple 
(11 comments)

Displaying bitmapped headlines

This one is a port of the Imagetext plugin for Smarty. So as I won't include the credit in this snippet please check them in the Smarty plugin itself here.

Requirements

In order to make it work, this is what you need : - A 'fonts' folder in your projects' data directory, in which you copy the .ttf files you need - The following helper :

function imagetext($text,$styleName=null,$params=array())
    {
        if(!$styleName){
            $styleName='default';
        }
        if (empty($text)) { return; }
        // Which style is in use
        $style=sfConfig::get('app_imagetext_styles');
        $style=$style[$styleName];
        // Param values overwrite style values
        foreach ($params as $key=>$value) $style[$key] = $value;
        // Error handling
        if (empty($style['font'])) { throw new sfViewException("imagetext: missing 'font' parameter"); return; }
        if (empty($style['size'])) { throw new sfViewException("imagetext: missing 'size' parameter"); return; }
        if (empty($style['bgcolor'])) { $style['bgcolor'] = 'FFFFFF'; }
        if (empty($style['fgcolor'])) { $style['fgcolor'] = '000000';  }
 
    ### Preferences
    $cacheDir=sfConfig::get('sf_web_dir').DIRECTORY_SEPARATOR.'images'.DIRECTORY_SEPARATOR.'imagetext'.DIRECTORY_SEPARATOR;
    $cacheUrl='imagetext/';
    $fontDir=sfConfig::get('sf_data_dir').DIRECTORY_SEPARATOR.'fonts'.DIRECTORY_SEPARATOR;  
    $text = preg_replace("=<br( /)?>=i", "\n", $text);
 
    // Which font is in use
    $font = $fontDir.$style['font'];
    // Hash of text and all parameters for the cache function
    $hash = md5(implode('',$style).$text);
    // The url of the created image
    $imgFile    = $cacheDir.$styleName.'_'.$hash.'.'.$style['format'];
    $imgUrl = $cacheUrl.$styleName.'_'.$hash.'.'.$style['format'];
 
    ### Use cached image if available
    if (file_exists($imgFile) && @$style['dev'] != true && @$style['dynamic'] != true) 
          if (@$style['urlonly'] == 'true') return $imgUrl; else
        return @$style['prehtml'].image_tag($imgUrl,array('style'=>'border: 0 px solid #00ff00','alt'=>preg_replace("=\r\n|\r|\n|\t=", ' ', htmlspecialchars($text)))).@$style['posthtml'];
 
    ### otherwise create it
    // Function to get a color handler of hex values
    if (!function_exists('fromhex'))
        {
        function fromhex($image,$string) {
            sscanf($string, "%2x%2x%2x", $red, $green, $blue);
            return ImageColorAllocate($image,$red,$green,$blue);
            }
        }
 
    ### create a four times larger image to improve kerning
    // The multiplier. The bigger the better the kerning and the typeface, but the slower the creation
    $multi = 4;
 
    // If "pixelfont" don´t use multiplier
    if ($style['pixelfont'] == 1) $multi = 1;
 
    // Calculate measures of image
    $bbox = imagettfbbox ($style['size']*$multi, 0, $font, $text);
    $xcorr = 0-$bbox[6]; // northwest X
    $ycorr = 0-$bbox[7]; // northwest Y
    $box['left']    = $bbox[6]+$xcorr+$style['x']*$multi;
    $box['height']  = abs($bbox[5])+abs($bbox[1]);
    $box['width']   = abs($bbox[2])+abs($bbox[0])+$style['x']*$multi;
    $box['top']     = abs($bbox[5]);
 
    // Create the big image
    $im = imagecreate ($box['width'], $box['height']);
    $bgcolor = fromhex($im,$style['bgcolor']);
    $fgcolor = fromhex($im,$style['fgcolor']);
    if ($style['pixelfont'] == 1) $fgcolor = -$fgcolor;
    imagettftext ($im, $style['size']*$multi, 0, $box['left'], $box['top'], $fgcolor, $font, $text);
 
    // Sample down the big image
    $width = $style['width']+$style['addx'];
    $height = $style['height']+$style['addy'];
 
    // Overwrite when height oder width is given
    if (empty($style['width'])) $width = $box['width']/$multi+$style['addx']+$style['x'];
    if (empty($style['height'])) $height = $box['height']/$multi+$style['addy']+$style['y'];
 
    $ds = imagecreatetruecolor ($width, $height);
    $bgcolor2 = fromhex($ds,$style['bgcolor']);
    imageFill($ds,0,0,$bgcolor2);
    imagecopyresampled($ds,$im,0,$style['y'],0,0,$box['width']/$multi, $box['height']/$multi,$box['width'], $box['height']);
    imagetruecolortopalette($ds,0,256);
    imagepalettecopy($ds,$im);
    ImageColorTransparent($ds,$bgcolor);
 
 
    // write whereto?
    if ($style['format'] == 'gif') ImageGIF ($ds,$imgFile); else ImagePNG ($ds,$imgFile);
    ImageDestroy ($im);
    ImageDestroy ($ds);
 
    // and display
    if ($style['dev']) $border = 1; else $border = 0;
  if ($style['urlonly'] == 'true') return $imgUrl;
        else
    return $style['prehtml'].image_tag($imgUrl,array('style'=>'border: '.$border.'px solid #00ff00','alt'=>preg_replace("=\r\n|\r|\n|\t=", ' ', htmlspecialchars($text)))).$style['posthtml'];
}

As you can see in this code styles can (at least 'default' MUST be) be configured in app.yml. This is an example style config :

    imagetext:
        styles:
            default:
                font:   hockey.ttf
                size:   18
                format: png

So now you can use the helper in your templates :

<?php use_helper('imageText') ?>
 
<? echo imagetext('This is my title') ?>
<p>Some blah blah</p>
<? echo imagetext('This is another headline style','anotherstyle') ?>

This helper uses GD so depending on which version you have installed, you might or might not handle PNG or GIF.

by whoknows on 2006-07-02, tagged helper  image  png  view 
(1 comment)

Helper: Embedding images in emails

Embedding images in emails as described in the symfony book has one drawback: it breaks the MVC paradigm by setting properties of the view (embedding the image) in the controller.

This is probably more obvious when embedding images such as company logos that are not part of a query result, but instead are 'static' visual enhancements of the view.

Besides being not good practice, this results in more code and potential mistakes when using several images, as you need to keep track of cid's both in the action and in the template.

This helper lets you treat images in emails similar to images in regular HTML views. There are no calls to $mail->addEmbeddedImage() needed in the action, embedding is done exclusively in the view, and you can forget about cid's.

Update: In the original version, an image used twice in the email would also be embedded twice, increasing email size. This bug is fixed.

Usage

Images are embedded like this:

<?php echo embedded_image($this, '/myImageDir/myImage.jpg' [, $altText [, $encoding [, $mimeType]]]); ?>

The parameters are:

$this: a reference to the current sfMailView

filename: the path to the image to be embedded

$altText: alternative name for attachment, default is ''

$encoding: encoding, default is base64

$mimeType: mime type, when omitted, mime type is determined using the file extension of the image

The Helper

function embedded_image(
    $mailView,
    $fileName,
    $altText = '',
    $encoding = 'base64',
    $mimeType = null
) {
 
    // get list of files already embedded
    $embeddedList = $mailView->getParameter('embedList', array(), 'email_helper');
 
    if (array_key_exists($fileName, $embeddedList)) {
 
        // if image was already embedded, use its old cid
        $embedString = $embeddedList[$fileName];
 
    } else {
 
        // find mime type
        if ($mimeType == null) {
            $mimeMap = array(
                'gif' => 'image/gif',
                'jpg' => 'image/jpeg',
                'png' => 'image/png'
            );
            $ext = strtolower(array_pop(explode('.', $fileName)));
            if (array_key_exists($ext, $mimeMap)) {
                $mimeType = $mimeMap[$ext];
            } else {
                $mimeType = 'image/jpeg';
            }
        }
 
        // increment embedded image count
        $imgID = $mailView->getParameter('embedIndex', 0, 'email_helper') + 1;
        $mailView->setParameter('embedIndex', $imgID, 'email_helper');
        $embedString = 'embID_' . $imgID;
 
        // add image to mail
        $mailView->getAttribute('mail')->addEmbeddedImage(
            $fileName,
            $embedString,
            $altText,
            $encoding,
            $mimeType
        );
 
        // remember embedded file
        $embeddedList[$fileName] = $embedString;
        $mailView->setParameter('embedList', $embeddedList, 'email_helper');
 
    }
 
    return '<img src="cid:' . $embedString . '" />';
 
}
by donharold on 2006-05-31, tagged email  helper  mvc  view 
(2 comments)

Popup tooltips

http://www.symfony-project.com/trac/ticket/540

Helper to work with http://tooltip.crtx.org Tooltip.js library (small js library that is using prototype and script.aculo.us, and so is the perfect match for symfony).

You will need to put that file into /helper/ directory, and put Tooltip.js (which you can get at the above-mentioned url) to the /sf/js/prototype/ directory.

Example of usage:

<?php use_helper('Tooltip') ?>
 
<?php echo tooltips_js('autoMoveToCursor=false showEvent=click', 'appear=true blindDown=true', 'blindUp=true', array('style.position' => 'absolute'), array('style.position' => 'absolute')) ?> 
 
<div id="some_id">some text</div>
 
<?php echo tooltip_div('some_id', 'css_class', array('style' => 'visibility: hidden'))?>
  Tooltip text.
</div>

Code is "phpdocumentor ready".

TooltipHelper.php as follows

<?php
 
require_once(sfConfig::get('sf_symfony_lib_dir').'/helper/JavascriptHelper.php');
 
 
/*
 * This file is part of the symfony package.
 * (c) 2006 Dmitry Parnas <parnas@rock.zp.ua>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
 
/**
 * TooltipsHelper.
 *
 * @package    symfony
 * @subpackage helper
 * @author     Dmitry Parnas <parnas@rock.zp.ua>
 * @version    
 */
 
/*
   Helpers to work with Tooltip.js [http://tooltip.crtx.org]
 */
 
 
/**
 * Builds open div tag ready with inforamtion for Tooltip.js
 *
 * Example:
 * <code>
 *   <?php echo tooltip_div('my_element_id', 'css_class_for_it', array('style' => 'visibility: hidden')) ?>
 * </code>
 *
 * @param string HTML id of the element this tooltip is for
 * @param css_class CSS class to skin tooltip in (if not default)
 * @param array Other attributes for the tag. You can also pass string suitable for _parse_attributes()
 *
 * @return string An HTML div string
 *
 */
function tooltip_div($element_id, $css_class = '', $options = array())
{                                                                                                                       
  $response = sfContext::getInstance()->getResponse();
  $response->addJavascript('/sf/js/prototype/prototype');
  $response->addJavascript('/sf/js/prototype/effects');
  $response->addJavascript('/sf/js/prototype/Tooltip');
 
  $options = _parse_attributes($options);
 
  $options['class'] = $css_class.' tooltip for_'.$element_id;
 
  return tag('div', $options, true);
}
 
 
 
/**
 * Builds script that sets optional settings for tooltips on the page.
 *
 * Example:
 * <code>
 *   <?php echo tooltips_js('autoMoveToCursor=false showEvent=click', 'appear=true blindDown=true', 'blindUp=true', array('style.position' => 'absolute'), array('style.position' => 'absolute')) ?>
 * </code>
 *
 * @param array Tooltip.js settings. You can also pass string suitable for _parse_attributes()   
 * @param array Ordered list of script.aculo.us effects you want to apply during tooltip show process. You can also pass string suitable for _parse_attributes()
 * @param array Ordered list of script.aculo.us effects you want to apply during tooltip hide process. You can also pass string suitable for _parse_attributes()
 * @param array Other settings for tooltip show process. You can also pass string suitable for _parse_attributes()
 * @param array Other settings for tooltip hide process. You can also pass string suitable for _parse_attributes()
 *
 * @return string An HTML script string.
 *
 */
function tooltips_js($options = array(), $show_effects = array(), $hide_effects = array(), $show_options = array(), $hide_options = array())
{
  $response = sfContext::getInstance()->getResponse();
  $response->addJavascript('/sf/js/prototype/prototype');
  $response->addJavascript('/sf/js/prototype/effects');
  $response->addJavascript('/sf/js/prototype/Tooltip');
 
 
  $options      = _parse_attributes($options);
  $show_effects = _parse_attributes($show_effects);
  $hide_effects = _parse_attributes($hide_effects);
  $show_options = _parse_attributes($show_options);
  $hide_options = _parse_attributes($hide_options);
 
  $code = '';
 
  foreach($options as $key => $value)
  {
    $value = _method_option_to_s($value);
 
    $value = (is_bool($value)) ? ($value === false) ? 'false' : 'true' : $value;
 
    $code .= '  Tooltip.'.$key.' = '.$value."\n";
  }
 
  $code .= _build_functions('show', $show_effects, $show_options);
  $code .= _build_functions('hide', $hide_effects, $hide_options);
 
  return javascript_tag($code);
}
 
 
function _build_functions($type, $effects = array(), $options = array())
{
  $code = '';
 
  if($effects) // there is no reason to build showMethod if there is no effects defined
  {
    $code .= '  Tooltip.'.$type.'Method = function (tooltip, options)'."\n";
    $code .= '  {'."\n";
 
    foreach($effects as $key => $value)
    {
      $code .= '    Effect.'.ucfirst($key).'(tooltip, options)'."\n";
    }
 
    if($options)
    {
      foreach($options as $key => $value)
      {
        $value = _method_option_to_s($value);
 
        $value = (is_bool($value)) ? ($value === false) ? 'false' : 'true' : $value;
 
        $code .= '    tooltip.'.$key.' = '.$value."\n";
      }
    }
 
    $code .= '  }'."\n";
 
    return $code;
  }
 
}
 
?>
by Adam Clarke on 2006-05-27, tagged helper  javascript  popup  tooltip 
(7 comments)

Localized image Helper

Returns localized image <img> tag bassed on the the user culture and the image file location in the filesystem.

Based on the technique recomended in the symfony advent calendar day twenty-three

Directory tructure:

Inside images directory, create one directory for each culture in use plus one (named 'no-loc' by default) for non-localized images or images that doesn't change for different cultures.

Example:

web/images/en_US
web/images/es_MX
web/images/fr_FR
................
web/images/non-loc

<?php
/**
 * Returns localized image <img> tag bassed on the the user culture
 * and the image file location in the filesystem
 * 
 * If the image file is found under the directory corresponding
 * the user culture, it returns its img tag,
 * else returns img tag under 'non-loc'directory.
 * 
 *  Structure example:
 * 
 *   web/images/en_US
 *   web/images/es_MX
 *   web/images/fr_FR
 *   ................
 *   web/images/non-loc
 * 
 * $source   -  relative path within culture directory
 * $options  -  is a normal options array passed to image_tag function
 * 
 */
function local_image_tag($source, $options = array())
{
  $images_path       = sfConfig::get('sf_root_dir') . '/web/images';
  $non_localized_dir = 'non-loc';
  $culture           = sfContext::getInstance()->getUser()->getCulture();
  $default_ext       = 'png';
 
  if (strpos(basename($source), '.') === false)
  {
    $source .= '.'.$default_ext;
  }
 
  if (!$culture || !(is_file($images_path.'/'.$culture.'/'.$source)))
  {
    return image_tag($non_localized_dir.'/'.$source, $options);
  }
 
  return image_tag($culture.'/'.$source, $options);
}
?>

Call example:

<?php
echo local_image_tag('some/image', 'alt=image');
/*
Will search in /web/images/<culture>/some/image.png,
and if file is not found, will return image tag in
/web/images/non-loc/some/image.png
*/
?>
by David Regla on 2006-05-25, tagged helper  i18n 
(1 comment)

Pagination navigation helper

In a template displaying a paginated list, you need to show the pager navigation. Create a PaginationHelper.php in lib/helper:

<?php
 
function pager_navigation($pager, $uri)
{
  $navigation = '';
 
  if ($pager->haveToPaginate())
  {  
    $uri .= (preg_match('/\?/', $uri) ? '&' : '?').'page=';
 
    // First and previous page
    if ($pager->getPage() != 1)
    {
      $navigation .= link_to(image_tag('/sf/images/sf_admin/first.png', 'align=absmiddle'), $uri.'1');
      $navigation .= link_to(image_tag('/sf/images/sf_admin/previous.png', 'align=absmiddle'), $uri.$pager->getPreviousPage()).' ';
    }
 
    // Pages one by one
    $links = array();
    foreach ($pager->getLinks() as $page)
    {
      $links[] = link_to_unless($page == $pager->getPage(), $page, $uri.$page);
    }
    $navigation .= join('  ', $links);
 
    // Next and last page
    if ($pager->getPage() != $pager->getLastPage())
    {
      $navigation .= ' '.link_to(image_tag('/sf/images/sf_admin/next.png', 'align=absmiddle'), $uri.$pager->getNextPage());
      $navigation .= link_to(image_tag('/sf/images/sf_admin/last.png', 'align=absmiddle'), $uri.$pager->getLastPage());
    }
 
  }
 
  return $navigation;
}

In your templates, display the pagination links like that:

<?php echo use_helper('Pagination') ?>
<?php echo pager_navigation($mypager, '@myrule') ?>
by Francois Zaninotto on 2006-05-20, tagged helper  pager  pagination  propel