Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "admin"

Multi sort in admin generator

Here we go !

The sort in admin generator is for a single field only, but in some complex list, it can be usefull to sort by multiple criterias. This is the main goal of this snippet. Those functions therefore override the ones of your auto-generated class. (1) And to display what are the ongoing sort criterias, you have to modify your '_list_th_tabular.php' file.

1 - In your 'action.class.php'

//
    /**
     * Add a sort criteria
     */
    protected function processSort ()
    {
        $sort = $this->getRequestParameter('sort');
        $type = $this->getRequestParameter('type');            
 
        // Register sort                 
        if ($sort) {
            $this->getUser()->setAttribute($sort, $type, 'sf_admin/produits/sort');
        }
    }    
 
    /**
     * Add the sort criterias to the query
     */
    protected function addSortCriteria(&$c)
    {
        $multisort = $this->getUser()->getAttributeHolder()->getAll('sf_admin/produits/sort');
 
        if ($multisort) {
            foreach($multisort as $sort_column => $sort_type) {
                $sort_column = Propel::getDB($c->getDbName())->quoteIdentifier($sort_column);
                if ($sort_type == 'asc') {
                    $c->addAscendingOrderByColumn($sort_column);
                }
                elseif ($sort_type == 'desc') {
                    $c->addDescendingOrderByColumn($sort_column);
                }
            }
        } else {
            // Default sort 
            $sort_column = Propel::getDB($c->getDbName())->quoteIdentifier('libelle');
            $c->addAscendingOrderByColumn($sort_column);            
        }
    }
 
    /**
     * Specific function for multi-sort
     */
    protected function processFilters ()
    {
        if ($this->getRequest()->hasParameter('filter'))
        {
            $filters = $this->getRequestParameter('filters');            
 
            // Multi-sort initialisation
            if (!is_array($filters)) {
                $this->getUser()->getAttributeHolder()->removeNamespace('sf_admin/produits/sort');
            }
 
            $this->getUser()->getAttributeHolder()->removeNamespace('sf_admin/produits/filters');
            $this->getUser()->getAttributeHolder()->add($filters, 'sf_admin/produits/filters');
        }
    }

2 - In '_list_th_tabular.php'

(for each sortable field)

<?php 
 
$multisort = $sf_user->getAttributeHolder()->getAll('sf_admin/produits/sort');
 
?>
 
    <th id="sf_admin_list_th_libelle">
        <?php 
        if (isset($multisort['libelle'])) {
            echo link_to('Libellé', 'Produits/list?sort=libelle&type='. ($multisort['libelle'] == 'asc' ? 'desc' : 'asc'));
            echo ' ('. $multisort['libelle'] . ')';
        } else {
            echo link_to('Libellé', 'Produits/list?sort=libelle&type=asc'); 
        }
        ?>
    </th>

'Produits' is the module name, 'libelle' is the field to sort.


Notes:

The 'reset button' of filters also initialize the multi-sort. The sort is made from the first field clicked to the last. That means, if you want a different primary sort you will have to use the reset filter button before.

PS: Obviously, this modification can be easly integrated in your backoffice theme, note that the default sort criteria set in the 'generator.yml' is used in the addSortCriteria function (from line // default sort)

COil :)

by Loïc Vernet on 2006-10-17, tagged admin  generator  multi  sort 
(6 comments)

Filtering with rows from another table

By default, the admin generator allows to filter the data with the rows from the table currently listed.

Here's how to extend this to data from other linked tables.

We'll consider the following example : a table "command" linked to a table "user". This very simple schema.xml shows the relation between these two tables :

<table name="buyer" phpName="BtqBuyer" >
  <column name="buyer_id" type="BIGINT" required="true" primaryKey="true"/>
  <column name="buyer_name" type="VARCHAR" size="255" required="true"/>
</table>
 
<table name="command" phpName="BtqCommand" >
  <column name="com_id" type="BIGINT" required="true" primaryKey="true"/>
  <column name="com_ref" type="VARCHAR" size="6" required="true"/>
  <column name="com_buyer_id" type="BIGINT" required="true"/>
  <foreign-key foreignTable="buyer" onDelete="" onUpdate="">
    <reference local="com_buyer_id" foreign="buyer_id"/>
  </foreign-key>
</table>

In the file generator.yml, add a partial in the filters parameter to print our specific filter :

filters: [com_ref, _btq_buyer]

The source code for the partial _btq_buyer.php (located in the templates directory) is :

<?php echo input_tag('filters[buyer]', isset($filters['buyer']) ? $filters['buyer'] : '') ?>

Now we have to add our specific filter in the filter process. To do this, we extend the addFiltersCriteria from the admin generator. This is done in the file actions.class.php by adding :

protected function addFiltersCriteria (&$c)
{
  if (isset($this->filters['buyer']) && $this->filters['buyer'] != '')
  {
    $c->add(BtqBuyerPeer::BUYER_NAME, strtr($this->filters['buyer'], '*', '%'), Criteria::LIKE);
    $c->addJoin(BtqBuyerPeer::BUYER_ID, BtqCommandPeer::COM_BUYER_ID);
  }
}

As you can see, we've even allowed the use of wildcard in our filter. Nice ;)

by marc Hugon on 2006-06-21, tagged admin  filter  generator 
(5 comments)

Upload Image and generate thumbnails with Admin Generator

With admin generator you can have a field specified to be a uploading file. Then as it is written in the doc, you can wirte in your generator.yml :

      picture:             
        name: Picture
        type: admin_input_file_tag
        upload_dir: picture
        params: include_link=picture include_remove=true

Then the file will be uploaded in /upload/picture directory

But maybe you want to restrict the size of the picture or add different files for different picture sizes.

Then this is an easy way to generate thumbnails in subdirectories of the main upload directory specified.

Just add the following method to your action class and adapt :

action.class.php:

  protected function updateProductFromRequest()
  {
    $product = $this->getRequestParameter('product');
 
    $thumbnails[]=array('dir' => '16x16', 'width' => 16, 'height' => 16);
    $thumbnails[]=array('dir' => '32x32', 'width' => 32, 'height' => 32);
 
    if (!$this->getRequest()->hasErrors() && isset($produit['picture_remove']))
    {
      foreach ($thumbnails as $thumbParam)
      {
        $currentFile = sfConfig::get('sf_upload_dir').'/picture/'.$thumbParam['dir'].'/'.$this->produit->getPhoto();
        if (is_file($currentFile)) unlink($currentFile);
      }
    }
 
    parent::updateProductFromRequest();
 
    if (!$this->getRequest()->hasErrors() && $this->getRequest()->getFileSize('product[picture]'))
    {
      $fileName=$this->product->getPicture();
      foreach ($thumbnails as $thumbParam)
      {
        $thumbnail = new sfThumbnail($thumbParam['width'], $thumbParam['height'],true,false); 
        $thumbnail->loadFile(sfConfig::get('sf_upload_dir')."/product/".$fileName);
        $thumbnail->save(sfConfig::get('sf_upload_dir').'/product/'.$thumbParam['dir'].'/'.$fileName, 'image/jpeg');
      }
    }
  }

As for uploaded files, fenerated thumbnails are not deleted when record is deleted.

by Sylvain PAPET on 2006-10-16, tagged admin  generator  thumbnail 
(6 comments)

Restrict objects displayed in the admin generator

If you want to restrict the objects displayed in the admin generator, you can add a permanent criteria by overriding the addFiltersCriteria() method:

protected function addFiltersCriteria(&$c)
{
  $c->add(CustomerPeer::DELETED, false);
}
by Fabien Potencier on 2006-05-21, tagged admin 

Conditional object actions for the admin generator

The list view of the admin generator currently always displays all defined object actions for each object. There is no way to display an object action for an object only if some condition on this object is met.

In order to extend the admin generator with this functionality only a small enhancement is required. You can either apply this change per module or create a new admin generator theme as described in the Symfony book.

The templates/_list_td_actions.php has to be extended to look roughly like this, depending on whether you already have your own modifications in there:

<?php if ($this->getParameterValue('list.object_actions')): ?>
<td>
<ul class="sf_admin_td_actions">
<?php foreach ($this->getParameterValue('list.object_actions') as $actionName => $params): ?>
  <?php if ( isset( $params['condition'] ) ): ?>
    [?php if ( <?php echo ( isset( $params['condition']['invert'] ) && $params['condition']['invert'] ? '!' : '') . '$' . $this->getSingularName(  ) . '->' . $params['condition']['function'] ?>( <?php echo ( isset( $params['condition']['params'] ) ? $params['condition']['params'] : '' ) ?> ) ):  ?]
  <?php endif; ?>
      <?php echo $this->addCredentialCondition($this->getLinkToAction($actionName, $params, true), $params) ?>
  <?php if ( isset( $params['condition'] ) ): ?>
    [?php endif; ?]
  <?php endif; ?>
<?php endforeach; ?>
</ul>
</td>
<?php endif; ?>
 

With this enhancement you can now use conditions for your actions in the generator.yml. The syntax should be pretty self-explanatory. An example would look like this:

object_actions:
  subscribe:    { name: Notify when changed, action: subscribe, icon: pencil_add.png }
    condition:
      function: isUserSubscribed
      params:   "$sf_user, 'test'"
      invert:   true
 

As you can see each object action now also takes a condition parameter, which again takes a number of parameter.

Enjoy!

by Georg Sorst on 2007-12-07, tagged actions  admin  condition  conditional  generator  object 
(10 comments)

Re-apply Symfony 1.2.7 security fix

This snippet tries to fix a problem that I had with Symfony deleting related data in a very sneaky way when you post an admin generated form.

The problem

This is related to the security fix posted here and later reversed here because it had side effects on custom partials among other things.

This happens when you have an admin generated form on a model having one or more many-to-many relationships and you don't want one or more of these relationships to appear in the form.
What you normally have to do is exclude the field xxx_list from the display section of the generator.yml file AND unset the field xxx_list in the configure method of the form.
Now it is very easy for people to forget that second part because they assume that Symfony been a DRY framework, you should not have to configure the same thing in 2 different places.

What happens if you forget to unset the field in the form is that Symfony creates a widget for the field but does not render it so that when the form is posted, it is the same as if an empty list had been posted for field xxx_list and Symfony will delete ALL many-to-many related records that may have existed.
I say this is sneaky because it happens in the background without any warning and your form does not show you any feedback of it because the corresponding widget is not rendered. By the time you realize it, you may have lost a lot of data.

If the security hole described in the links above has a lot of ifs attached to it, the data loss described here is only a matter of when it will happen if you forget to unset a many-to-many relation field.

The solution

This is only for Doctrine, but could be modified for propel.

This snippet re-applies the fix mentioned above, and allows its customization to limit it to just many-to-many relations.

You should still unset the the fields in the form as directed, but if you or another developer forgets, the data loss should not happen.

In the data directory of your application, create the sub-directories /generator/sfDoctrineModule/admin/parts.

Create there a file named configuration.php and replace its content with this snippet:

[?php
 
    /**
     * <?php echo $this->getModuleName() ?> module configuration.
     *
     * @package    ##PROJECT_NAME##
     * @subpackage <?php echo $this->getModuleName()."\n" ?>
     * @author     ##AUTHOR_NAME##
     * @version    SVN: $Id: configuration.php 12474 2008-10-31 10:41:27Z fabien $
     */
    class Base<?php echo ucfirst($this->getModuleName()) ?>GeneratorConfiguration extends sfModelGeneratorConfiguration
    {
    <?php $config = new ProjectConfiguration();
    $pluginConfig = $config->getPluginConfiguration('sfDoctrinePlugin');
    $doctrinePath = $pluginConfig->getRootDir(); ?>
 
    <?php include $doctrinePath.'/data/generator/sfDoctrineModule/admin/parts/actionsConfiguration.php' ?>
 
    <?php include $doctrinePath.'/data/generator/sfDoctrineModule/admin/parts/fieldsConfiguration.php' ?>
 
      /**
       * Gets a new form object.
       *
       * @param  mixed $object
       *
       * @return sfForm
       */
      public function getForm($object = null)
      {
        $class = $this->getFormClass();
 
        $form = new $class($object, $this->getFormOptions()); 
 
        $this->fixFormFields($form); 
 
        return $form; 
      }
 
 
      /**
      * Removes visible fields not included for display.
      *
      * @param sfForm $form
      */
      protected function fixFormFields(sfForm $form)
      {
        $fieldsets = $this->getFormFields($form, $form->isNew() ? 'new' : 'edit');
 
        // flatten fields and collect names
        $fields = call_user_func_array('array_merge', array_values($fieldsets));
        $names = array_map(array($this, 'mapFieldName'), $fields);
 
        foreach ($form as $name => $field)
        {
          if (!$field->isHidden() && !in_array($name, $names))
          {
            unset($form[$name]);
          }
        }
      }
 
 
      /**
       * Gets the form class name.
       *
       * @return string The form class name
       */
      public function getFormClass()
      {
        return '<?php echo isset($this->config['form']['class']) ? $this->config['form']['class'] : $this->getModelClass().'Form' ?>';
    <?php unset($this->config['form']['class']) ?>
      }
 
      public function getFormOptions()
      {
        return array();
      }
 
      public function hasFilterForm()
      {
        return <?php echo !isset($this->config['filter']['class']) || false !== $this->config['filter']['class'] ? 'true' : 'false' ?>;
      }
 
      /**
       * Gets the filter form class name
       *
       * @return string The filter form class name associated with this generator
       */
      public function getFilterFormClass()
      {
        return '<?php echo isset($this->config['filter']['class']) && !in_array($this->config['filter']['class'], array(null, true, false), true) ? $this->config['filter']['class'] : $this->getModelClass().'FormFilter' ?>';
    <?php unset($this->config['filter']['class']) ?>
      }
 
    <?php include $doctrinePath.'/data/generator/sfDoctrineModule/admin/parts/filtersConfiguration.php' ?>
 
    <?php include $doctrinePath.'/data/generator/sfDoctrineModule/admin/parts/paginationConfiguration.php' ?>
 
    <?php include $doctrinePath.'/data/generator/sfDoctrineModule/admin/parts/sortingConfiguration.php' ?>
 
      public function getTableMethod()
      {
        return '<?php echo isset($this->config['list']['table_method']) ? $this->config['list']['table_method'] : null ?>';
    <?php unset($this->config['list']['table_method']) ?>
      }
 
      public function getTableCountMethod()
      {
        return '<?php echo isset($this->config['list']['table_count_method']) ? $this->config['list']['table_count_method'] : null ?>';
    <?php unset($this->config['list']['table_count_method']) ?>
      }
 
      public function getConnection()
      {
        return null;
      }
    }
 

This file will serve as a template to generate the generator's base configuration class for each module in the cache.

It will replace the original one from sfDoctrinePlugin with these differences:

It will unset all fields in the form that are not in the display section of the admin config.

If you want to limit this to only many-to-many relationship fields, you can replace in the fixFormFields method:

if (!$field-&gt;isHidden() &amp;&amp; !in_array($name, $names))
 

with

 if (!$field-&gt;isHidden() &amp;&amp; !in_array($name, $names) &amp;&amp; $form-&gt;getObject()-&gt;getTable()-&gt;hasRelation(preg_replace(&quot;/_list$/&quot;, &quot;&quot;, $name)))
 

Do not forget to clear your cache: symfony cc

Note that if you want to disable or modify this functionality in a particular module, all you have to do is override the getForm() method of the module's generator configuration class found in the /lib directory of the module in the file my_moduleGeneratorConfiguration.class.php.

Note also that in the original fix, the fixFormFields method was in the sfModelGeneratorConfiguration which is the base call for all module's generator configuration and it is now repeated for each module, which is not ideal, but should not cause major problems.

by J. Philip on 2009-07-22, tagged admin  generator 

Change detection for admin generator

This JavaScript code registers a change detection mechanism in every form field and notifies the user about unsaved changes. No changes need to be applied to existing modules/actions. Additionaly, the TinyMCE helper can be changed in order to detect changes there as well.

Only requirement: the links for leaving the page need to be in a container with id "header". This can of course be changed.

Add this to the head of the page (or to an external js-file):

var changesDetected = false;
 
/**
 * Registers a change detection mechanism that notifies users about unsaved changes whenever they click on a link.
 */
function registerChangeDetection() {
    /**
     * Notifies user about unsaved changes
     */
    function notifyAboutChanges(e) {
        if(changesDetected){
            //My choice: modal dialog using modalbox (http://www.wildbit.com/labs/modalbox/)
            //Modalbox.show('<div class=\'warning\'><p>Before continuing, you need to save you changes.</p> <input type=\'button\' value=\'Ignore changes\' onclick=\'changesDetected=false;Modalbox.hide()\' style=\'color: #999\' /> <input type=\'button\' value=\'OK\' onclick=\'Modalbox.hide()\' /></div>',  {title: 'Warning', width: 300});
            //Alternative:
            alert('Before continuing, you need to save your changes.');
            return false;
        }
    }
 
    /* Add change detection to every form field */
    if(document.forms.sf_admin_edit_form != null) {
        var elements = Form.getElements(document.forms.sf_admin_edit_form);
        elements.each(function(item) {
            item.onchange = function(e) { changesDetected = true; }
        });
    }
 
    /* Add an onclick handler to every link in the container with id "header" */
    var links = $$('#header a');
    links.each(function(item) {
        if(!(item.onclick instanceof Function)) { //Avoid overwriting existing onclick handlers
            item.onclick = notifyAboutChanges;
        }
    });
}
 

Register the change detection in the body tag:

<body onload="registerChangeDetection()">
 

I also modified the sfRichTextEditorTinyMCE helper in order to use TinyMCE 3 (currently beta). Here is the code relevant to change detection to be put into TinyMCE.init({...}) (Go to http://wiki.moxiecode.com/index.php/TinyMCE:API/tinymce.Editor/onChange for more information):

setup: function(ed) {
    var i = 0;
    ed.onChange.add(function(ed, l) {
        if(i == 0) i++ //Ignore the first change
        else changesDetected = true;
    });
}
 
by Michel Weimerskirch on 2007-12-05, tagged admin  changes  detection  generator  javascript  tinymce 

input_in_place_editor_grid_tag

creates an inplace editable html table from a database table. save this helper in your_project/lib/helper/input_in_place_editor_gridHelper.php dir

Special thanks to Christian.

There is a problem with saving the snippet here.I will try to update it later.

But for a normal (user interface) following action can be used:

public function executeUpdategrid()
{
    $value=trim(strip_tags($_POST['value']));// new value of the cell being updated
$pFieldValue=intval($this->getRequestParameter('pFieldValue'));// value of primary key of the record in the table, ie id value
$fieldNo=intval($this->getRequestParameter('fieldNo'));// index of the field coming from input_in_place_grid_tag
 
/*
to permit the fields just we want to update
here 2,3 and 4 are indexes of these fields in $rs, and this is for security
this is used only if secure parameter is set in grid options
*/
 
# these are just examples to illustrate secure usage
if($fieldNo==2)
$fieldName='Account.FIRSTNAME';
elseif($fieldNo==3)
$fieldName='Account.LASTNAME';
elseif($fieldNo==4)
$fieldName='Account.BIRTHD';
elseif($fieldNo==5)
$fieldName='Settings.EMAIL';
else
die("Invalid Table Field!");// invalid field number.(just for security.Complete table field names can be used in an admin application)
 
 
$split=explode(".",$fieldName);
$tableName=$split[0];
 
// to find primary key field name
$fields=call_user_func(ucfirst(strtolower($tableName)).'Peer::getFieldNames');
 
if($fields[0])
{
    # primary key field name
    $pFieldName=$tableName.".".strtoupper($fields[0]);
    # update corresponding field
    $conn=Propel::getConnection();
    $sql="UPDATE $tableName SET $fieldName='$value' WHERE $pFieldName=$pFieldValue";
    $conn->executeQuery($sql);
    $this->value=$value;// set the value to print out in the template "updategridSuccess.php"
}
else
$this->value=null;
}

or both can be used in same action as follows:

/**
 * account actions.
 *
 * @package    1insaat
 * @subpackage account
 * @author     Ahmet Ertek
 * @version    1.0
 * * @desc     Implements input_in_place_editor_grid helper's cell update.(this helper is not standart, just a custom helper).This action is not in use now.See /profile/templates/settingsSuccess.php
 */
public function executeUpdategrid()
{
 
    $value=trim(strip_tags($_POST['value']));// new value of the cell being updated
    $pFieldValue=intval($this->getRequestParameter('pFieldValue'));// value of primary key of the record in the table, ie id value
    $fieldNo=intval($this->getRequestParameter('fieldNo'));// index of the field coming from input_in_place_grid_tag
    $fieldName=trim($this->getRequestParameter('fieldName'));// name of the field to be updated
    $fieldName=str_replace("_",".",$fieldName);// replace _ with. This is because no_script_name can be setto "on" in settings.yml of app
 
    /*
    to permit the fields just we want to update
    here 2,3 and 4 are indexes of these fields in $rs, and this is for security
    this is used only if secure parameter is set in grid options
    */
    if(!$fieldName)
    {
        # these are just examples to illustrate secure usage, change these field names with yours
        if($fieldNo==2)
        $fieldName='Account.FIRSTNAME';
        elseif($fieldNo==3)
        $fieldName='Account.LASTNAME';
        elseif($fieldNo==4)
        $fieldName='Account.BIRTHD';
        elseif($fieldNo==5)
        $fieldName='Settings.EMAIL';
        else
        die("Invalid Table Field!");// invalid field number.(just for security.Complete table field names can be used in an admin application)
    }
 
    $split=explode(".",$fieldName);
    $tableName=$split[0];
 
    // to find primary key field name
    $fields=call_user_func(ucfirst(strtolower($tableName)).'Peer::getFieldNames');
 
    if($fields[0])
    {
        # primary key field name
        $pFieldName=$tableName.".".strtoupper($fields[0]);
        # update corresponding field
        $conn=Propel::getConnection();
        $sql="UPDATE $tableName SET $fieldName='$value' WHERE $pFieldName=$pFieldValue";
        $conn->executeQuery($sql);
        $this->value=$value;// set the value to print out in the template "updategridSuccess.php"
    }
    else
    $this->value=null;
 
}
?>

Note that to use this grid for admin app you may use

$options['secure']=>false;
by ahmet ertek on 2007-05-12, tagged admin  ajax  datagrid  grid  grid  inplaceeditor  inplaceeditorgridtag  inplacegrid 
(1 comment)

Set create default value to filter defined value for a specific field

This is a very usefull and simple tip if you want to defined default value in edit form (only on creation of a new record) with the value that the user defined in the list filter.

An exemple for a product module which define the category select field with the value of the same field in the list filter.

action.class.php

  public function executeEdit ()
  {
    $filters = $this->getUser()->getAttributeHolder()->getAll('sf_admin/product/filters');
    if (!$this->getRequestParameter('product_id', 0) && isset($filters['category_id']))
    {
      $this->product = new Product();
      $this->produit->setCatergoryId($filters['category_id']);
    }
    parent::executeEdit();
  }
 
  protected function getProductOrCreate ($product_id = 'product_id')
  {
    if (isset($this->product)) return $this->product;
    else return parent::getProductOrCreate($product_id);
by Sylvain PAPET on 2006-10-20, tagged admin  filter  generator 
(3 comments)

Override save() and delete() methods in the admin generator

Sometimes, you want to do something just before or just after saving or deleting an object in the admin generator. To do that, you can override the saveObject() and/or deleteObject() method in your actions.class.php file:

protected function saveCustomer($customer)
{
  // pre save hook
  $customer->setManagerId($this->getUser()->getManagerId());
 
  // call the parent save() method
  parent::saveCustomer($customer);
 
  // post save hook
}

You can also bypass the parent method if you want.

protected function deleteCustomer($customer)
{
  $customer->isDeleted(true);
 
  // save the customer object
  $this->saveCustomer($customer);
 
  // bypass the deletion by not calling the parent method
}
by Fabien Potencier on 2006-05-21, tagged admin 
(4 comments)

Multiple applications within one project - an other way

In the Cookbook there is a way for multiple applications whitin one project, but this is not worked sometimes.

There is an other way what is working with Apache. Add the next line to the back-end virtual-host configuration file before the </VirtualHost> line.

AccessFileName .htaccess_backend

In the project web directory copy the .htaccess file to .htaccess_backend. In .htaccess_backend file change the next line:

RewriteRule ^(.*)$ index.php [QSA,L]

to:

RewriteRule ^(.*)$ backend.php [QSA,L]

Don't forget to restart the apache.

by Tamas Amon on 2007-11-28, tagged admin  apache  server 

admin Generator and Thumnail generation in file uploads with symfony 1.2

Hello to the desperate,

As you might have probably already mentioned, the old way, how to produce thumbnails stopped working. This is because of the brand new admin generator, using an all new model based on forms, which doesn't support the old methods.

this is the new way, how to get it working again:

put this into your generator.yml file (take care for the correct identation, do not use tabs!!):

      edit:
        title: Photo uploads 
        fields:
          foto:
            type:       admin_input_file_tag
            upload_dir: /uploads/pictures/
            params:     include_link=/uploads/pictures/ include_remove=true
 

and then enhance the file lib/form/YourclassForm.php with the following method:

  protected function processUploadedFile($field, $filename = null, $values = null) {
    // first of all do what this is supposed to do
    $fn = parent::processUploadedFile($field, $filename, $values);
    // and now we can finally start doing additional stuff after the upload *hurra*
    if ($fn != "") {
        // if there is a file, that has been saved
        // multidimensional array that defines the sub-directories to store the thumbnails in
        $thumbnails[]=array('dir' => '90x90', 'width' => 90, 'height' => 90);
        $thumbnails[]=array('dir' => '200x200', 'width' => 200, 'height' => 200);
 
        foreach ($thumbnails as $thumbParam)
        {
          $currentFile = sfConfig::get('sf_upload_dir').'/pictures/'.$thumbParam['dir'].'/'. $fn;
          if (is_file($currentFile)) unlink($currentFile);
        }
        foreach ($thumbnails as $thumbParam)
        {
            $thumbnail = new sfThumbnail($thumbParam['width'], $thumbParam['height'],true,false); 
            $thumbnail->loadFile(sfConfig::get('sf_upload_dir')."/pictures/".$fn);
            $thumbnail->save(sfConfig::get('sf_upload_dir').'/pictures/'.$thumbParam['dir'].'/'.$fn, 'image/jpeg');
        }
    }
    // do not forget to return the value of the parent-function, otherwise it stops working
    return $fn;
  }  
 

Hope, you can use that snippet!

lg Christoph

by Christoph Christ on 2009-05-07, tagged admin  file  generator  sfthumbnail  symfony  uploads 
(2 comments)

keep original filename when using admin generator upload

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:

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 Roberto Carvajal on 2007-06-13, tagged admin  filename  generator  upload 
(1 comment)

How to use the admin generator along with propel inheritance

The problem

Say that you have a model object Son that inherits from the model object Father, via propel inheritance. You want to create an admin generator interface for the object Son. The problem is that propel does not generate a SonPeer class, so you'll have to call:

symfony propel-init-admin Father

but the list function will list all the Father objects instead of the Son objects only. Same problem with the create function that will create a Father object, not a Son object.

The solution

Here is a solution. You will have to overload getFatherOrCreate and addFiltersCriteria:

// add this in the actions.class.php of your admin module
  protected function addFiltersCriteria(&$c)
  {
    $c->add(FatherPeer::CLASS_KEY, FatherPeer::CLASSKEY_SON);
    parent::addFiltersCriteria($c);
  }  
 
  protected function getFatherOrCreate ($id = 'id')
  {
    $son = parent::getFatherOrCreate($id);
 
    if ($son->isNew()) // if it is a new one then we create a Son object
      $son = new Son();
   else
     $this->redirect404Unless($son->isSon()); // we check that we really got a son object
 
   return $son;
  }

Now everything will work as if you were using the Son object.

by Olivier Verdier on 2006-05-25, tagged admin  generator  inheritance  propel 
(2 comments)

How to create a custom admin generator theme ...

... without having trouble with your PEAR directory.

If you use the admin generator but don't like the generated html code, you either can use a custom css file to redo the style, or you have to copy the original theme from

/path/to/$PEAR/symfony/generator/sfPropelAdmin/default

and redo the html here. The problem with that technique is, that you have to manage this PEAR directory from now on, instead having the new theme located in your project folder. That's bad, because your theme will be lost if you clear your PEAR installation or decide to uninstall the symfony version and re-install another one.

After doing some research in the forums and docs, I tried to create the theme in my project directory, with the expected folder structure. I created a folder structure in my projects data dir, like this:

/path/to/$PEAR/data/generator/sfPropelAdmin/$THEME_NAME

I just copied all directories and files from the default sfPropelAdmin theme to this folder, but this didn't worked out. symfony wasn't able to find this theme. So, I created a symbolic link to this folder:

ln -s \
/path/to/$PROJECT_NAME/data/generator/sfPropelAdmin/$THEME_NAME \
/path/to/$PEAR/data/symfony/generator/sfPropelAdmin/$THEME_NAME

Now, you I modify the template to fit my needs, e.g. reset the css classes, add a new table structure and so on. After upgrading/uninstalling/installing a new copy of symfony, don't forget to check if the symbolic link still exists. If not, just re-create it, and everything should work.

If your webserver and php understoods the following of symbolic links (Windows doesn't), you can use this new theme for your sfPropelAdmin generated modules via the theme configuration handle in your generator.yml:

generator:
  class:              sfPropelAdminGenerator
  param:
    model_class:      ModelClass
    theme:            $THEME_NAME
by Pierre Minnieur on 2006-09-17, tagged admin  custom  generator  pear  theme 
(4 comments)

Conditional object actions for the admin generator Explained

ReUse of: Conditional object actions for the admin generator By Georg Sorst

The list view of the admin generator currently always displays all defined object actions for each object. There is no way to display an object action for an object only if some condition on this object itself is met.

In order to extend the admin generator with this functionality only a small enhancement is required. You can either apply this change per module or create a new admin generator theme as described in the Symfony book.

The templates/_list_td_actions.php has to be extended to look roughly like this, depending on whether you already have your own modifications in there.

I use sf v1.4.11, for make this enhancement...

First, you should find the partial _list_td_actions.php found in

lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/data/generator/sfDoctrineModule/admin/template/templates/_list_td_actions.php 
 

And modify the original code like this:

<td>
    <?php if ($this->configuration->getValue('list.object_actions')): ?> 
    <ul class="sf_admin_td_actions">
    <?php foreach ($this->configuration->getValue('list.object_actions') as $name => $params): ?>
        <?php if ( isset( $params['condition'] ) ): ?>
            [?php if ( <?php echo ( isset( $params['condition']['invert'] ) && $params['condition']['invert'] ? '!' : '') . '$' . $this->getSingularName( ) . '->' . $params['condition']['function'] ?>( <?php echo ( isset( $params['condition']['params'] ) ? $params['condition']['params'] : '' ) ?> ) ): ?] 
        <?php endif; ?>
 
        <?php if ('_delete' == $name): ?>
            <?php echo $this->addCredentialCondition('[?php echo $helper->linkToDelete($'.$this->getSingularName().', '.$this->asPhp($params).') ?]', $params) ?>
        <?php elseif ('_edit' == $name): ?>
            <?php echo $this->addCredentialCondition('[?php echo $helper->linkToEdit($'.$this->getSingularName().', '.$this->asPhp($params).') ?]', $params) ?>
        <?php elseif ('_show' == $name): ?>
            <?php echo $this->addCredentialCondition('[?php echo $helper->linkToShow($'.$this->getSingularName().', '.$this->asPhp($params).') ?]', $params) ?>
        <?php else: ?>
            <li class="sf_admin_action_<?php echo $params['class_suffix'] ?>">
                <?php echo $this->addCredentialCondition($this->getLinkToAction($name, $params, true), $params) ?>
            </li>
        <?php endif; ?>
 
        <?php if ( isset( $params['condition'] ) ): ?>
            [?php endif; ?]
        <?php endif; ?>
    <?php endforeach; ?>
    </ul>
    <?php endif; ?>
</td>
 

Second, in the generator.yml you should add aditional lines like

actionName:
            label:          actionLabel
            action:         executeAction
            #Now the enhancement =D
            condition:
              # function is the name of function in the model::functionName and must return boolean
              function:     functionName 
              # params are the params to send to functionName
              params:       "$model->getDbField(), $sf_user, 'test'"
              # invert is used if you need invert the result of functionName
              invert:       false
 

Third and final step, go to

lib/model/doctrine/model.class.php
 

and create the functionName like:

/**
* $param1 is $model->getDbField()
* $param2 is $sf_user
* $param3 is 'test'
*/
public function functionName($param1, $param2, $param3){
        //code personal
        return boolean;
    }
 

Final, important!

php symfony cache:clear 
 

Well that's all, sorry for my bad English =S Thanks Georg Sorst and Google Translator

by Krikor Krikorian on 2011-05-26, tagged actions  admin  condition  conditional  generator  object 

admin generator getAllColumns except

My First snippet ever AND, my first contribution to the programming world... hope you like it ;)

This snippet is for those of you who, just like me, have 30 fields in one class and doesnt want to write them all down in the admin generator and erase the ones you don't want.

if you want to display all field except one, here's how:

generator:
  class:              sfPropelAdminGenerator
  param:
    model_class:      users
    theme:            default
    list:
      sort:           [updated_at, desc]
      hide:           [the_fields_i_dont_want]
 

Notice the "HIDE" option.

If you want to display all fields AND you want to add a partial, it gets a little tricky (and ugly, but it does the trick until something better is created)

generator:
  class:              sfPropelAdminGenerator
  param:
    model_class:      users
    theme:            default
    list:
      sort:           [updated_at, desc]
      display:        [_my_partial, <?php echo implode(', ',JobPeer::getFieldNames(BasePeer::TYPE_FIELDNAME)) ?>]
      max_per_page:   20
 

Notice the "DISPLAY" option ... I know, it's ugly to have php in a yml file ...

You can also combien the two and get all columns and hide the ones you don't want .

P.S. I should have wrote this earlier but this snippet wouldn't exists if it weren't for pookey from #symfony (irc). THanks again man ;)

by Mob[st]er2 on 2008-01-10, tagged admin  display  generator  generators  hide 

Display Primary Key in Admin Generator Edit Form

By default, we can't display primary keys in the admin generator edit view.

But I wanted to do just that.

Here is an example for the module/table "article", and the primary key column "id" :

Beware of the tricky spelling of the partial name in the generator.yml file if, like me, you like underscores...

by Vincent Texier on 2007-08-01, tagged admin  form  generator 
(1 comment)

Double List for ManyToMany relationships (deprecated)

I needed the object_admin_double_list helper of the admin generator for my regular projects (without using admin generator). So I ported the admin_double_list helper a bit and now I want to publish this for you.

The admin_double_list helper is for selecting multiple items from a pool of items using ManyToMany relationships. For example to associate a user to multiple groups.

The helper itself has two parts: - two helper functions - two javascript functions

After that I have an example how the three controller, view and model parts handle this helper.

The snippet itself

Helper file

/**
 * two multiline select tags with associated and unassociated items
 *
 * @return string
 * @param object object
 * @param string method of object
 * @param array options
 * @param array html options of select tags
 **/
function double_list($object, $method, $options = array(), $html_options = array())
{
  $options = _parse_attributes($options);
 
  // get the lists of objects
  list($all_objects, $objects_associated, $associated_ids) = _get_object_list($object, $method, _get_option($options, 'through_class'));
 
  // options
  $html_options['multiple'] = _get_option($html_options, 'multiple', true);
  $html_options['size'] = _get_option($html_options, 'size', 10);
  $html_options['class'] = 'double_list';
 
  $label_assoc = _get_option($options, 'associated_label', 'Zugehörige Gruppen');
  $label_all   = _get_option($options, 'unassociated_label', 'Gruppenliste');
  $name1 = _get_option($options, 'associated', 'associated');
  $name2 = _get_option($options, 'unassociated', 'unassociated');
  $form = _get_option($options, 'form_id', 'editForm');
 
  // unassociated objects
  $objects_unassociated = array();
  foreach ($all_objects as $object)
  {
    if (!in_array($object->getPrimaryKey(), $associated_ids))
      $objects_unassociated[] = $object;
  }
 
  // select tags
  $select1 = select_tag($name1, options_for_select(_get_options_from_objects($objects_associated), '', $options), $html_options);
  unset($html_options['class']);
  $select2 = select_tag($name2, options_for_select(_get_options_from_objects($objects_unassociated), '', $options), $html_options);
 
  // output skeloton
  $html =
'<div style="float:left; padding-right: 20px;">
  <label for="%s">%s</label>
  %s
</div>
<div class="float:left; padding-right: 20px; padding-top: 20px">%s<br />%s</div>
<div class="float:left;">
  <label for="%s">%s</label>
  %s
</div>
<div style="clear:both"></div>';
 
  // include js library
  $response = sfContext::getInstance()->getResponse();
  $response->addJavascript('/js/double_list.js', 'last');
 
  return sprintf($html,
      $name1, $label_assoc, $select1,
      link_to_function(image_tag('resultset_previous'), "double_list_move(\$('{$name2}'), \$('{$name1}'))"),
      link_to_function(image_tag('resultset_next'), "double_list_move(\$('{$name1}'), \$('{$name2}'))", 'style=display:block'),
      $name2, $label_all, $select2,
      $form
    );
}
 
/**
 * retrieve object list via propel
 *
 * @return array
 * @param object root object
 * @param string retrieving method
 * @param string name of satellite class
 **/
function _get_object_list($object, $method, $middleClass)
{
  // get object
  $object = $object instanceof sfOutputEscaper ? $object->getRawValue() : $object;
 
  // get all objects
  $objects = sfPropelManyToMany::getAllObjects($object, $middleClass);
  // get related objects
  $objects_associated = sfPropelManyToMany::getRelatedObjects($object, $middleClass);
  // get ids
  $ids = array_map(create_function('$o', 'return $o->getPrimaryKey();'), $objects_associated);
 
  return array($objects, $objects_associated, $ids);
}
 

You probably want to modify the look of the list using css (like I do). So you can change the $js variable like you want f.e. adding class names. And you perhaps also want to change the image paths (I used two icons of the famfamfam icon library).

The javascript

Put the code below in a file called double_list.js in your js directory. (For individual path and file name, modify the double_list helper, search for $response)

function double_list_move(src, dest)
{
  for (var i = 0; i < src.options.length; i++)
  {
    if (src.options[i].selected)
    {
      dest.options[dest.length] = new Option(src.options[i].text, src.options[i].value);
      src.options[i] = null;
      --i;
    }
  }
}
 
function double_list_submit()
{
  // get all selects with double list class
    selects = $$('select.double_list');
 
    selects.each(function(element){
        for (var i = 0; i < element.options.length; i++)
            element.options[i].selected = true;
    });
 
    return true;
}
 

Example

I would like to show an example how to handle this helper in the three patterns.

The example is easy. Assigning an user to many groups.

Model layer

We have to build a table which handles the ManyToMany relationship.

user_group:
  _attributes:
    phpName:    UserGroup
  group_id:
    type:       integer
    primaryKey: true
    foreignTable:groups
    foreignReference:id
    onDelete:   cascade
  user_id:
    type:       integer
    primaryKey: true
    foreignTable:users
    foreignReference:id
    onDelete:   cascade
 

Don't forget to rebuild all db stuff and to clear the cache.

Presentation layer

Now we display the double_list:

<?php echo double_list($user, 'getUserGroups', 'through_class=UserGroup associated=groups unassociated=not_groups associated_label=Associated Groups unassociated_label=Group list') ?>
 

The first parameter is the user object, than the method of the object retrieving the UserGroup records. I think the options are clear.

Controller layer

At last we have to save the selection of the user. Before doing this we have to delete all UserGroup objects of the user, because we would assign it twice, if the item was selected before.

// clear group data to save it again
$c = new Criteria();
$c->add(UserGroupPeer::USER_ID, $user->getId());
UserGroupPeer::doDelete($c);
 
// save groups
$groups = $this->getRequestParameter('groups');
if ($groups)
{
  foreach ($groups as $id)
  {
    $group = new UserGroup();
    $group->setGroupId($id);
    $group->setUserId($user->getId());
    $group->save();
  }
}
 

It was a bit too long. As I said, this is a port of the original object_admin_double_list helper, which can be practically only used with the Admin Generator.

Please check the snippet, because I use this in a little bit more customized version.

by Halil Köklü on 2007-07-04, tagged admin  form  propel 
(2 comments)

Quick Search for admin_double_list

Sometimes in the generator you have a large listing of elements in a admin_double_list.

Well I wanted to write a quick little autocomplete text box that would search for results and when you selected one, it would be added into the associated select box.

In your generator add a new partial: edit: display: [ _quick_search ]

Create a file in your templates directory called _quick_search.php

Copy this code in there:

===CODE===

<?php $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(sfConfig::get('sf_prototype_web_dir').'/js/controls'); $response->addStylesheet(sfConfig::get('sf_prototype_web_dir').'/css/input_auto_complete_tag'); ?> <input type="text" id="quick_search" name="quick_search"/>

<div class="auto_complete" id="quick_search_auto_complete" style="position: absolute; left: 445px; top: 421px; width: 113px; opacity: 0.17329; display: none;">

</div>

<script type="text/javascript">

//<![CDATA[

function addSelection (li)

{

$('associated_titles').options[ $('associated_titles').options.length] = new Option (li.childNodes[0].nodeValue, li.id);

}

new Ajax.Autocompleter('quick_search', 'quick_search_auto_complete', '/backend_dev.php/search', { updateElement: addSelection });

//]]>

</script>

===CODE===

Make sure you change your url from /backend_dev.php/search to whatever your action that will search your database and your good to go!

Just FYI: You can use the Symfony helper autocomplete_tag () because it does not allow for passing the updateElement parameter.

by Myke Hines on 2007-06-29, tagged admin  admindoublelist  admingenerator  ajax  generator 
(3 comments)

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 

select_range_tag() for admin generator

Sometimes you may want to give the user the possibility to select a certain range of numbers in a numeric field in the admin generator. An example would be a rating between 1 and 5. This is where this snippet comes in handy.

Add the helper code below to one of your helper groups, that preferably is included in your standard_helpers. Then you can use it like in your generator.yml like follows:

generator: 
  ...
  param:
    ...
    edit:
      display: [..., percentage, ...]
      fields:
        ...
        percentage: { type: select_range_tag, params: min=0 max=100 step=5 }

Supplying a step is optional, it defaults to 1.

Helper code:

function select_range_tag($name, $selected, $options)
{
  $options = _parse_attributes($options);
 
  $select_options = range(_get_option($options, 'min', 1), _get_option($options, 'max', 5), _get_option($options, 'step', 1));
 
  return select_tag($name, options_for_select(array_combine($select_options, $select_options), $selected));
}
 
function object_select_range_tag($object, $method, $options = array(), $default_value = null)
{
  $options = _parse_attributes($options);
 
  $value = _get_object_value($object, $method, $default_value);
 
  return select_range_tag(_convert_method_to_name($method, $options), $value, $options);
}
by Martin on 2007-05-07, tagged admin  generator  input  range  select  tag 

Including custom partials in admin generator pages

Here is a method i developed to include some custom partials at the end of the edit action of a module which adapts admin generator.

i just added this at the end of editSuccess.php file of my generator theme ($sf_data_dir/generator/sfPropelAdmin/<myCustomTheme>/templates/editSuccess.php)

[?php // include xtra partials ?]
<?php if ($this->getParameterValue('edit.partials')): ?>
<?php
$partials = $this->getParameterValue('edit.partials');
foreach( $partials as $partial => $vars ) : ?>
[?php include_partial('<?php echo $this->getModuleName() ?>/<?php echo $partial ?>', array('<?php echo $this->getSingularName() ?>' => $<?php echo $this->getSingularName() ?> <?php foreach ($vars as $var): ?> , '<?php echo $var ?>' => $<?php echo $var; endforeach; ?>)); ?]
<?php endforeach; ?>

then you just have to add the partials in the generator.yml like this :

edit:
  partials:
    partial1: [ var1a, var1b ]
    partial2: [ var2a, var2b ]

where var.. are the variables you want to include in each partial.

this can be easily made for list success also.

by Kostas Papadimitriou on 2007-01-08, tagged admin  custom  generator  include  partial  partials 
(2 comments)