Code snippets for symfony 1.x

Navigation

Snippets by user whoknows

propel behavior to extend classes with separate tables

I needed to have many subclasses inherited from a main class.

To do that with propel, you need to have all your classes in one big table. I wanted to have separate tables.

Then I discovered the propel behaviors and it does that very well !

Let's see an example with a master class "element" end a subclass "subElement" :

by whoknows on 2007-08-13, tagged behavior  inheritance  model  propel 

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)

keep original filename for backend uploaded files (admin generator)

This is a simple way I came up for keeping original filenames for the files I upload using the admin generator.

We need an extra field in our database schema to store the original filename, lets say our file field it's called "lecture" then our original lecture's filename field would be "original_lecture", our object will be called "Course".

Remember to rebuild your object model before the next step.

Once we have the 2 fields there we need to ignore the second field inside generator.yml, the way to do this is to alter the display parameter and only list the fields you want to edit.

Then we overwrite our actions and add the following code:

class courseActions extends autocourseActions
{
  protected function updateCourseFromRequest()
  {
    $this->course->setOriginalLecture($this->getRequest()->getFileName('course[lecture]'));
    parent::updateCourseFromRequest();
  }
}

This will automatically setup the original filename on the right field.

Now, we move to the frontend, we need an action to download our file so we can alter the request and add our filename on the fly, you can add the following code on any frontend action but for clarity sake I've created a module called "download" that I'll use to download any of my files.

So, inside our download module we add the following action:

class downloadActions extends sfActions
{
  /**
   * Executes lecture action
   *
   */
  public function executeLecture()
  {
    $id = $this->getRequestParameter('id');
    $this->course = CoursePeer::retrieveByPk($id);
    $this->getResponse()->addHttpMeta('Content-Disposition', 'attachment; filename="'.$this->course->getOriginalLecture().'"');
  }
}

As you can see, we added a Content-Disposition header to our request, this header will force to download a file and name it according to our original_lecture field.

Finally, on our view, we need to do 2 things.

1) Disable the layout, this is done under view.yml just add:

lectureSuccess:
  has_layout: off

2) Inside lectureSuccess.php we add:

<?php
 
readfile(sfConfig::get('sf_upload_dir').'/lectures/'.$course->getLecture());
 
?>

This will output the file name stored in the filesystem, remember to provide the right path, this is just an example :)

Finally, to download the file you just need to follow this link:

http://www.yourhost.com/download/lecture?id=FILEID

That's all, your browser will prompt you with a save file dialog.

by whoknows on 2007-06-13, tagged admin  filename  generator  upload 

Saving records w/ Doctrine from a form using object helpers

// This is meant to be in actions.class.php as one of your actions. // Note how it redirects to your view action after saving.

public function executeSave() { $req = $this->getRequest();

$q = new Doctrine_Query();
$user = $q-&gt;from(&#039;UserInfo&#039;)-&gt;where(&#039;id = ?&#039;,$this-&gt;getRequestParameter(&#039;id&#039;))-&gt;execute()-&gt;getFirst();
 
$user-&gt;merge($req-&gt;getParameterHolder()-&gt;getAll());
$user-&gt;save();
 
$this-&gt;redirect(&#039;user/view?did=save&amp;id=&#039;.$user-&gt;id);

}

by whoknows on 2007-06-12, tagged doctrine  objecthelper  save 

easy cookie validation for user login systems

If you're building a site with a user login system (like Askeet) and your PHP is configured to store session variables in a client-side cookie, the following snippet will improve the usability for users who have disabled cookies.

The following example assumes you already have at least a simple user login system on your site. If not, check out the Askeet tutorial for a great example to get you started.

All users who have disabled cookies will be unable to log into any site that relies on client-side cookies to store session variables. If you don't validate cookies and provide notification, these users will never know why they couldn't log in to your site.

Try the following if you'd like to see this firsthand.

Unfortunately, Askeet also provides the perfect example here as well. (Sorry guys!)

Not much happened, right? You're not logged in and you don't know why. (Well, you do now.)

What we're going to do is augment an existing login system to provide users with notification that your site requires cookies.

To do this, we're going to attempt to set a cookie on the login page and verify that it was created once the login form is submitted. Since a cookie is stored only after the page is loaded, it takes two pages to validate the user's setting. Fortunately, the login process takes two pages!

Edit your existing login action code by adding the following two setCookie() functions.

The first setCookie() sets the validation cookie when the login action is first loaded. (If you use your login on several pages as a component/partial, move this first setCookie() to the login form component.)

The second setCookie() deletes the validation cookie upon successful login. If this cookie is not deleted, the validation could return a false positive if the user disables cookies at a later date. (When cookies are disabled, new cookies can't be written, but old cookies can be read.)

module_dir/actions/actions.class.php:

public function executeLogin()
{
  if ($this->getRequest()->getMethod() != sfRequest::POST)
  {      
    sfContext::getInstance()->getResponse()->setCookie('cookies_enabled', 'true', time()+60*60*24, '/');    
  }
  else
  {
    sfContext::getInstance()->getResponse()->setCookie('cookies_enabled', '', 0, '/');    
    $this->redirect('@homepage');
  }
}

Enable cookie validation by adding the following code to your existing login validation. The validator takes two parameters, cookie_name and cookie_error. The cookie_name parameter is, oddly enough, the name of the cookie we'll use to validate and it should match the cookie set in the above code.

The other parameter, cookie_error, is the error message that will be returned if the user has cookies disabled. Typically, validation errors are phrases like "Invalid username" or "Password must contain 6-8 characters". But we're going to use "cookies_disabled" and I'll show you why in a few minutes.

IMPORTANT: The cookie validation should occur first.

module_dir/validate/login.yml:

methods:
  post: [username]
 
names:
  username:
    required:     true
    required_msg: 
    validators:   [cookiesEnabledValidator, userValidator]
 
cookiesEnabledValidator:
  class:          myCookiesEnabledValidator
  param:
    cookie_name:  cookies_enabled
    cookie_error: cookies_disabled
 
userValidator:
  class:          myLoginValidator
  param:
    password:     password
    username_error: Invalid username.
    password_error: Invalid password.

Copy the following code to one of your lib directories. Since it only deals with the login action, I choose to keep it in my user module's lib directory.

module_dir/lib/myCookiesEnabledValidator.class.php:

class myCookiesEnabledValidator extends sfValidator
{    
  public function initialize($context, $parameters = null)
  {
    // initialize parent
    parent::initialize($context);
 
    // set defaults
    $this->setParameter('cookie_name', sfContext::getInstance()->getStorage()->getParameter('session_name'));
    $this->setParameter('cookie_error', 'This site requires cookies.');
    $this->getParameterHolder()->add($parameters);
 
    return true;
  }
 
  public function execute(&$value, &$error)
  {    
    if (sfContext::getInstance()->getRequest()->getCookie($this->getParameter('cookie_name')) === null)
    {
      $error = $this->getParameter('cookie_error');
      return false;
    }
    return true;
  }
}

Now, since it takes two pages to set and read a cookie it wouldn't make sense to return the user to the form right away. If they enable their cookies they'll still have to submit the form twice before they'll login successfully. I prefer to send them to a page which notifies them that the site requires cookies and explains how they can enable them. (Plus, this gives us the extra click we need to set that validation cookie!)

So we'll check for the cookie validation error before returning to the form so we can redirect the user to our help page if necessary. The error we check for in the username parameter must match the cookie_error we defined in the login.yml.

module_dir/actions/actions.class.php:

public function handleErrorLogin()
{
  if (sfContext::getInstance()->getRequest()->getError('username') == 'cookies_disabled')
  {
    $this->redirect('@about_cookies');
  }
  return sfView::SUCCESS;
}

So that's it. Throw in an about cookies page and you're all set.

If I made any mistakes, I apologize. It's 5am on a school night.

by whoknows on 2007-05-11, tagged cookie  login  session  storage  usability  validation 

Generic action to save edit-in-place fields

One stumbler for me was that I didn't realize that the form created by the input_in_place_editor_tag() helper only submits one key/value pair (value=xxx), so to tell the action which PK to use or which object field to update, you have to pass that information on the form URL (field and id).

One way to solve it would be to write a different action for each field you want to update, but I wanted something I could reuse without writing additional actions, so I added two GET parameters:

So, in my showSuccess.php template:

<h1 id="eipTitle"><?php echo $comment->getTitle() ? $comment->getTitle() : "click to edit" ?></h1>
<?php echo input_in_place_editor_tag(
    'eipTitle',
    'comment/ajaxUpdate?field=title&commentid='.$comment->getCommentid(),
    array(
    'cols'   => 40,
    'rows'   => 1,
    ))
 
   ?>

BTW, you can read about the valid options you can pass in the third argument to input_in_place_editor_tag() at the script.aculo.us Wiki

And for the action, I made it as generic as possible (actually it would be nice if Propel generated an action like this next to the regular executeUpdate action):

private function raiseEipError ($message) 
  {
    $this->logMessage($message, 'err');
    return $this->renderText( "ERROR:  $message" );
  }
 
  public function executeAjaxUpdate()
  {
    $id = $this->getRequestParameter('commentid');
    $field = $this->getRequestParameter('field');
    $this->logMessage("commentid:$id and field='$field'", 'debug');
 
    // Check for required params.  
    // Return a nice message to the user and in the log if there's a problem.
    if (! $field) 
      return $this->raiseEipError( "No field parameter passed in form action" );
    $valid_fields = CommentPeer::getFieldNames(BasePeer::TYPE_FIELDNAME);
    if (! in_array($field, $valid_fields)) 
      return $this->raiseEipError( "Invalid field parameter '$field' passed in form action.\n".
                                   " Valid fields: (" . implode(", ", $valid_fields) . ")" );
    if (! $id) 
      return $this->raiseEipError( "No ID parameter passed in form action" );
 
    $comment = CommentPeer::retrieveByPk($id);
    if (! $comment) 
      return $this->raiseEipError( "Object with ID $id not found" );
 
    $comment->setByName($field, $this->getRequestParameter('value'), BasePeer::TYPE_FIELDNAME);
    $comment->save();
    return $this->renderText( $comment->getByName($field, BasePeer::TYPE_FIELDNAME) );
  }
by whoknows on 2007-05-04, tagged action  ajax  editinplace  form  inputinplaceeditortag  update 

Autosetting Created_by and Updated_by fields in all models

Propel can create Models with autoset created_at and Updated_at field, but dont include autoset for Created_by and Updated_by field, because this fields are most aplication dependent. We can set Propel to build models with autossetting Created_by and Updated_by fields (ao any other fields) extending SfObjectBuilder. In the forum message 7535 Tamcy give us a good example. Here I post a SFObjectBuilder extension class (in this example I check if using SfGuardPlugin for user Class).

<?php
/* I recomend put this class out Symfony Folder.
*  If you put this class in some project folder
* you can use include_once 'symfony.addon.propel.builder.SfObjectBuilder.php'
require_once 'sfObjectBuilder.php';
* @author     Boris Duin 
 */
class SfObjectAdvBuilder extends SfObjectBuilder
{
  */Extend Method addSave
  protected function addSave(&$script)
  {
    $tmp = '';
    parent::addSave($tmp);
 
    $date_script = '';
 
    $user_updated = false;
    $user_created = false;
    foreach ($this->getTable()->getColumns() as $col)
    {
      $clo = strtolower($col->getName());
 
      if (!$user_created && $clo == 'created_by')
      {
        $user_created = true;
        $date_script .= "
    if (\$this->isNew() && !\$this->isColumnModified('created_by'))
    {
      \$user = sfContext::getInstance()->getUser();
      if (\$user instanceof sfGuardSecurityUser)
        \$this->setCreatedby(\$user->getUsername())
    }
";
      }
      else if (!$user_updated && $clo == 'updated_by')
      {
       $user_updated = true;
        $date_script .= "
    if (\$this->isModified() && !\$this->isColumnModified('updated_by'))
    {
      \$user = sfContext::getInstance()->getUser();
      if (\$user instanceof sfGuardSecurityUser)
        \$this->setUpdatedby(\$user->getUsername())
    }
";
      }
    }
 
    $tmp = preg_replace('/{/', '{'.$date_script, $tmp, 1);
    $script .= $tmp;
  }
}

Note: I write this class for synfony 0.9.x versión. Yesterday I was refactoring this class for 1.0.x version, but I leave this in my house, :(. To set propel to use this class you must edit config/propel.ini. Replace this line

for

Example: If you put this class in your Project/Lib folder you can set the line:

Now, when you run symfony propel-build-model, propel will use this class and add autoset code for created_by and Updated_by field. Here an example of models class generated by propel:

public function save($con = null)
    {
    if ($this->isNew() && !$this->isColumnModified('created_by'))
    {
      $user = sfContext::getInstance()->getUser();
      if ($user instanceof sfGuardSecurityUser)
        $this->setCreatedby($user->getUsername());
    }
 
    if ($this->isNew() && !$this->isColumnModified('created_at'))
    {
      $this->setCreatedAt(time());
    }
 
    if ($this->isModified() && !$this->isColumnModified('updated_by'))
    {
      $user = sfContext::getInstance()->getUser();
      if ($user instanceof sfGuardSecurityUser)
        $this->setUpdatedby($user->getUsername());
    }
 
    if ($this->isModified() && !$this->isColumnModified('updated_at'))
    {
      $this->setUpdatedAt(time());
    }
by whoknows on 2007-01-29, tagged model  propel 

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 

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)