Digital Base – Blog » Bart Vanderstukken

How to unsecure admin generated modules in Symfony

You gotta love the admin generator… There’s only one problem: it’s secured by default (according to the symfony site).
But I want to use these modules in a non-secured environment.
Adding credentials: [] in the admin generator does not work… Why?
Look at your automoduleactions. You’ll find a preExecute function:

  public function preExecute()
  {
    $this->configuration = new eventGeneratorConfiguration();
 
    if (!$this->getUser()->hasCredential($this->configuration->getCredentials($this->getActionName())))
    {
      $this->forward(sfConfig::get('sf_secure_module'), sfConfig::get('sf_secure_action'));
    }
 
    $this->dispatcher->notify(new sfEvent($this, 'admin.pre_execute', array('configuration' => $this->configuration)));
 
    $this->helper = new eventGeneratorHelper();
  }

This code is always executed before any action.

if (!$this->getUser()->hasCredential($this->configuration->getCredentials($this->getActionName())))

the getCredentials function:

  public function getCredentials($action)
  {
    if (0 === strpos($action, '_'))
    {
      $action = substr($action, 1);
    }
 
    return isset($this->configuration['credentials'][$action]) ? $this->configuration['credentials'][$action] : array();
  }
}

The problem is that hasCredential(array()) returns false.
My solution:
Override the hasCredential function in myUser

  public function hasCredential($credential, $useAnd = true)
  {
    //for usage in generator => + as credential will return hasCredential=true (even if user has no credentials at all)
    if($credential === true) return true;
    //btw => you could have checked for an empty array of credentials which is what the generator is returning
    return parent::hasCredential($credential, $useAnd);
  }

Now to make it work, put in generator.yml

  config:
     actions:
        _delete:        { credentials: admin }
        _list:            { credentials: + }
        index:           { credentials: + }
#        _new:         { credentials: + }
#        _edit:         { credentials: + }

Ps: You could also use false & -

Simplifying Queries: Using sum, count, etc in Propel 1.3

My old post handled Propel 1.2.

This is the way to do it in Propel 1.3

1
2
3
4
5
6
7
    $c = new Criteria();
    $c->add(FavoriteDatePeer::EVENTDATAPROPOSAL_ID, $this->getId());
    $c->addSelectColumn("SUM(".FavoriteDatePeer::SCORE.") as rank");
 
    $stmt = FavoriteDatePeer::doSelectStmt($c);
    $row = $stmt->fetch();
    return $row['rank'];

FusionCharts Symfony Plugin: dbFusionChartsPlugin

We're
currently writing an OO class to create FusionCharts
quickly, easily & intuitive, because the existing FusionCharts.php class is not OOP and, honestly, we thought we could do better…
Our package is based on and tested against the FusionCharts Free v2 package. It's written
symfony independent, but we provide a helper, for easy use in symfony.

The symfony plugin, dbFusionChartPlugin, is
in its alpha state now, because it only includes basic single & multi-series chart support. Although the basic code is ready, it needs to be extended, to work with all chart types, but a mediocre programmer should be able to understand its structure and adapt/extend it to his needs. We deeply appreciate all help and suggestions!

Symfony
helper

[code]FC helper ex[/code]

This helper creates the necessary JavaScript.

[code]FC javaScript[/code]

Documentation

You can find the dbFusionChart phpDoc at digitalbase.eu/dbFusionChart/doc/index.html

Logic

As you can see in the FusionCharts Documentation, you need to define your chart's data in an XML file. Our dbFusionChartXML class uses the php DOM extension to encapsulate that XML logic by providing methods to add categories and (datasets with) sets. It has (for now) the descendants dbFC_Single & dbFC_Multi. The latter will make sure there are always as many sets in each dataset as there are categories, when adding a dataset or category.

Stay tuned

We have recently added a check in the addCategory function so it
wouldn't add the same category twice, no matter how many times it's
being called, e.g. when looping Propel objects to init a dbFusionChartXML. It's checks like these that need to be added, among much much more… So stay tuned!

Xajax driven CriteriaBuilder for Propel / php

Earlier this week, I talked about our custom datagrid for Propel and
how it was being rewritten to interact with our CriteriaBuilder (read
more
). Well, I’ll give you another glance into our coding
efforts.

Goal

We wanted to make
an object, which would manipulate the data being shown in our
grid_propel. For normal people, this means: to filter the data.

Early on, we
decided it had to ‘build a Criteria‘ (because Propel rules!), which
then could be used by our grid_propel.

Structure

The
CriteriaBuilder is in essence nothing but an array of FilterData
objects, which is indexed by the FilterColumn’s name (Propel peer
constant), so there could only be one FilterData object per
FilterColumn.
Of course, there’s more to it, about a thousand
lines of code actually (phpDoc included), but almost half of that are
xajax functions for user interaction, and then there are methods like
addAvailableColumn & qAddAvailableColumn to initialize the
CriteriaBuilder, and internal functions, which I’m not all going to
explain here. I’ll touch some of them in the next section.

Behind
the scenes

The __toString
function, will loop all FilterData objects and create a selector,
with their column name or with a more human readable alias, using our
XHTML package, which I’ll perhaps tell you more about on a
quite/rainy day. It’s an object-oriented package to quickly create
readable and XHTML valid code, I can tell you that.

CriteriaBuilder column selector

The add filter
button will trigger the xajax function addFilter, which adds a
UserFilter to the FilterData object related to the selected/passed
value and then updates the ‘criteriaform’.

Criteriabuilder UserFilter example

This criteriaform
shows for every UserFilter a ‘criteriaLine’, with a combination box
(disabled if only one line present), a filter method selector and
something to determine the search value.

Clicking ‘apply
filters’ will trigger another xajax function, which will build the
Criteria based on the current UserFilters, and pass it to all
registered CriteriaClients through the CriteriaClient interface
method processCriteria. This is an implementation of what Java
programmers probably know as the Listener pattern, meaning that an
object implementing the CriteriaClient interface registers itself as
Listener/CriteriaClient, by calling
CriteriaBuilder::addClient(CriteriaClient).

This means that the
CriteriaBuilder can serve any (and more than one) object implementing
the CriteriaClient interface, not just our grid_propel!

The ‘remove this
filter’ button will trigger a xajax fuction, which will remove the
UserFilter from the FilterColumn’s UserFilter array, reorder its
keys, and update the criteriaform.
The ‘clear all
filters’-button, naturally, deletes all UserFilters and also updates
the CriteriaForm. Because there are no filters left, the criteriaform
will disappear again.
Both functions will also immediately update
the grid by calling buildCriteria (which calls processCriteria on its
clients).

I’ve recently
added a ’save filter’-button, which makes this box appear,

CriteriaBuilder save filter combination

giving the user
the option to save his filter combination. Which is then linked to
the CriteriaBuilder’s name and saved in our database. When a
CriteriaBuilder is ‘__tostringed’, it will search for these filters,
and if one’s found, you’ll get something like this.

CriteriaBuilder load saved UserFilter

Selecting a saved
filter will once again trigger a xajax function, which will load the
filter (update criteriaform and build Criteria, and thus updating the
grid).
For now, these are personal filters, because they’re linked
to the user who’s created them. Later, we could easily extend the
code, so they can be shared, or something like that…
Note that
by linking the saved filters to the CriteriaBuilder’s name, we’re
able to use the same filters on different pages, by naming the
CriteriaBuilders the same.

Everything
configurable!

Now, you know
what’s happening behind the scenes when a user uses our
CriteriaBuilder, but that’s not even half the story. Still using our
user grid, I’ll explain how the CriteriaBuilder’s FilterData objects
are being initialized.

We
use this code on our page (FYI: our pages are also created using an
object-oriented framework)

[code]CB_pageContent[/code]

So…
We initialize our grid_propel, grid_users (see this post), and
specify its starting Criteria. You can also neglect this, then a new
(empty) Criteria will be used. Next, we use the CiteriaBuilder’s
constructor to pass the grid as CriteriaClient, set its name, and set
the original Criteria, which is used to restore the grid to its
original state.

Now,
that I come to think of it, this should be stored in the grid_propel,
not in the CriteriaBuilder, because the original Criteria can be
different per client!
You see this is code in progress… We
actually don’t use multiple CriteriaBuilders nor CriteriaClients
ourselves, yet… :)
I simply added the method getOriginalCriteria
to the interface class CriteriaClient. CriteriaBuilder::buildCriteria
now retrieves its ‘Criteria to work with’ via that method, thus from
the grid_propel, which of course has an extra variable
originalCriteria now. Grid_propel::setCriteria is now

[code]CB_setCriteria[/code]

Voila,
solved!

Ok,
to continue… $cb->qAddUserColumnSet
is a convenience method.
Admitted, these functions should better
be in an extending class, but nobody’s perfect :)
And since we’re
the only ones using it…

[code]CB_qAddUserColumnSet[/code]

As
you can see, this method adds the columns we say the grid can be
filtered on. These could also be columns which aren’t shown in the grid_propel.

[code]CB_qAddAvailableColumn[/code]

So…
In our case, we use the CriteriaBuilder’s constants, for example
CriteriaBuilder::FILTERSET_TEXT
stands for the array

[code]CB_ex_array[/code]

UserFilter
types

This
defines which ‘type’ of UserFilter will be used. For each constant,
there’s a descendant of UserFilter. They complete UserFilter by
defining the abstract __toString function. So each descendant will be
shown different.

TYPE_TEXT

simple inputfield

UserFilter type text
TYPE_BOOLEAN

simple select

UserFilter type boolean
TYPE_DATE

JavaScript calendar widget

UserFilter type date
TYPE_SPECIFIC

selector with values available in database for that column (=
dynamic)

UserFilter type specific
TYPE_SPECIFIC_SWITCH

special specific, it has extra filtermethods ‘contains’ & ‘does
not contain’. When this filter is selected the search value
determinator, will change into an inputfield.

First

UserFilter type specific_switch normal

Then

UserFilter type specific_switch for (does not) contains
Of course, you can combine as many filters as you want.
combination of multiple UserFilters

Symfony / Propel – Subquerys

To reduce the query count in your web application, you might consider using subqueries. I'll show you howto do that in propel/symfony :

[code]propel - subqueries[/code]

It's that easy :)

Simplifying Queries: Using sum, count, etc in Propel

Simplifying Queries: Using sum, count, etc in Propel

$c->addSelectColumn('sum('.dbTimeBlockPeer::TOTAL.') as total');
$rs = dbTimeBlockPeer::doSelectRS($c);
while ($rs->next()) { $total = $rs->getInt(1); }

So, simply use addSelectColumn. You have to work with the ResultSet offcourse, since you won’t be able to populateObjects.

In this case you can simply use ‘1′ in getInt(column index), since we’re sure only one column is being fetched. Note that the index starts at 1 and not at 0.

Now, imagine a case where you are not sure what the index is (it could also change in the project’s life cycle). Then you can use, dbTimeBlockPeer::translateFieldName(dbTimeBlockPeer::TOTAL, BasePeer::TYPE_COLNAME, BasePeer::TYPE_NUM)), which will return the position of column ‘total’.

This needs no explanation, but I’ll give it anyway: translateFieldName accepts as first argument a string (source), the source type name and the destination type name. You can go and look in the basePeer which type you need.

basedbTimeBlockPeer (generated by Propel)

private static $fieldNames = array ( BasePeer::TYPE_PHPNAME => array ('Id', 'UserId', 'ProjectId', 'Ticketnr', 'Changeset', 'Description', 'TimeIn', 'TimeOut', 'Total', ), BasePeer::TYPE_COLNAME => array (dbTimeBlockPeer::ID, dbTimeBlockPeer::USER_ID, dbTimeBlockPeer::PROJECT_ID, dbTimeBlockPeer::TICKETNR, dbTimeBlockPeer::CHANGESET, dbTimeBlockPeer::DESCRIPTION, dbTimeBlockPeer::TIME_IN, dbTimeBlockPeer::TIME_OUT, dbTimeBlockPeer::TOTAL, ), BasePeer::TYPE_FIELDNAME => array ('id', 'user_id', 'project_id', 'ticketnr', 'changeset', 'description', 'time_in', 'time_out', 'total', ), BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, ) );

Custom Propel Grid Update

As you can read on Gijs’ Lifelog, he started developing a custom datagrid a while ago. The idea was to create some kind of module/object, which would be used for generating datagrids fast, easy and intuitive, without too much code.

Over time, it has been extended and rewritten to interact with our CriteriaBuilder (read blog).

This week, I’ve been refactoring/cleaning the code to clearly separate the code for our Propel driven datagrid from the basic html grid whose data could come from anywhere.

Underlying code

Due to several questions about releasing this code, here is some insight in how the code is structured, at this moment. Perhaps it will be released in the future, perhaps even as a symfony plugin, but we’re still indecisive about that.

So, we have an abstract grid class which contains all code needed to generate a basic html table. It has an array $columns, which holds grid_columns, and an $options array to manipulate its behaviour. One of them is the option â “pagable”, and if enabled the grid has to have a grid_pager set.

Grid_pager is an interface which defines what methods a pager (at least) should have and our grid_propel_pager implements that interface.

On __toString, the abstract function initfirstload is called. Its purpose is to set all columns and options, i.e. to define the grid. Then it calls display, which calls initdisplay. This function will do everything that has to be done before actually displaying the grid. The difference is that tostring is called once, and thus initfirstload too, while display & initdisplay are called multiple times through xajax.

The displayHeader function calls displayHeaderon each column. The displayRows method is abstract. For a very simple grid it could call displayContent on each column.A grid_column holds all data for showing an html column, like its header name, alignment, whether it’s shown, sortable, etc… It has the following display methods (among others): displayHeader & displayContent, which obviously return th and td strings.

Now that’s all pretty abstract !

Here’s how we use all of that in our grid_propel class, which uses grid_column_propels. The idea is that we could represent a table’s data fetched via Propel.

Grid_propel extends the grid_paged class (containing the code for a grid to be paged via xajax), which extends the abstract grid class. Some display methods are overridden and the abstract displayRows method is implemented. It loops all objects we retrieve from the grid_propel_pager (which implements the grid_pager interface, remember). In the loop, displayContent is called on eachcolumn and the object is passed.

Grid_column_propel extends grid_column_method which extends grid_column. In grid_column_propel::displayContent, we use call_user_func_array to retrieve the content to be shown. It also has a guessGetter method which guesses the method based on the column’s peer constant name, so you don’t have to provide it!

And voila, we have a fully Propel driven html table, which is sorted and paged via xajax.

Put it to work!

If that’s Chinese to you, don’t worry because of the great principle of encapsulation:

YOU DON’T HAVE TO UNDERSTAND IT!

Or to quote the textbook

You don’t have know how a car works to drive it!

Simply define your object class and columns that need to be shown. That’s all! Grid_propel will do the work for you!

Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class grid_users extends grid_propel {
 
 public function __construct($name) {
   parent::__construct($name,"_User");
 }
 
 public function init_firstload() {
   $this->option_enable("sortable");
 
   $gridcontent =  page_minimal::get_grid_contentobject();
   $bid = new grid_column_propel("id",_UserPeer::ID);
   $bid->display = false;
   $this->column_add_unique($bid);
 
   $this->column_add(new grid_column_propel($gridcontent->getContent("Login"), _UserPeer::LOGIN, "", 40));
   $this->column_add(new grid_column_propel($gridcontent->getContent("First Name"), _UserPeer::NAME_FIRST, "", 40));
   $this->column_add(new grid_column_propel($gridcontent->getContent("Family Name"), _UserPeer::NAME_FAMILY, "", 40));
   $this->column_add(new grid_column_propel($gridcontent->getContent("Email"), _UserPeer::EMAIL, "", 100));
   $this->column_add(new grid_column_propel($gridcontent->getContent("Country"), _CountryPeer::COUNTRY, array("get_Country", "getCountryShort"), 25, "center"));
 }
}

When we use this in a page (and register the necessary xajax functions), this will result in:

simple custom propel grid

You can also use your own methods (not propel-generated)!

This really blows the grid wide open. You can call any method for the grid’s object class. You can even pass an array of methods. Every method is called on the result for the last call. The last method should of course return a string.

For example, adding

1
2
3
$view = new grid_column_method("", "getLink", 10, "center");
$view->setArgs(array(false, true));
$this->column_add($view);

will result in a clickable image that will pop up an information window:

custom propel grid with own methods

Note that you can now use grid_column_method, the parent class of grid_column_propel. But, TIMTOWTDI, you could also use grid_column_propel, with an empty peer constant name.

1
$view = new grid_column_propel("", "", "getLink", 10, "center");

If you provide a name for such a non-propel column, you should also disable sorting for that column, since it has no peer constant name.

Foreign key values

Our user grid also contains a special column, Country. It’s actually a foreign key for the _user table.

1
$this->column_add(new grid_column_propel($gridcontent->getContent("Country"), _CountryPeer::COUNTRY, array("get_Country", "getCountryShort"), 25, "center"));

We provide _CountryPeer::COUNTRY, the column used for sorting, so the results would be alphabetically sorted instead of being sorted by the foreign key. Our grid_propel takes care of the necessary joins.

We also provide an alternative method array, otherwise the getter is guessed by grid_column_propel, which would result in the methodarray(->get_Country->getCountry).

Other descendants of grid_column

We have also created a special grid_column, grid_action, which almost does the same as the information image. But again, it groups code.Grid_action_url extends this class and its goal is to easy generate such images.

1
$this->action_add(new grid_action_url("application_edit.jpg","","/userid/", "", 15));

Will produce an image linked to /userid/$id

1
$this->action_add(new grid_action_js_delete("_User","Delete"));

Will produce a “stop”-image which calls _User::Delete, via xajax, on the object related to the row.

That’s all pretty neat, isn’t it? But, of course, that ain’t enough !

Last week I’ve added some functionality to export the datagrid to an excel workbook using the Spreadsheet_Excel_Writer class. Grid_column_propel accepts extra arguments

1
$excelwidth=null, $excelname=null, $exportmethod=null, $exportargs=null, $export=true"));

By default all columns will be exported. But if you want the export button to be shown, you need to enable the boolean export for the grid_propel.

You could also add columns (propel or method) to the grid which will only appear in the excel file, by simply disabling display for that column. Soon, I’ll add an extra argument $format, to control the layout in excel, column per column. And some constants to represent often used formatting.

All together, our user grid looks like this

Our user grid

If that still isn’t enough for you…

There’s also a class called grid_hook, which uses a grid_column_checkbox. Without going into details, this code

1
$this->addHook(new grid_hook(page_minimal::get_grid_contentobject()->getContent("edit"),"multiEdit"));

generates the edit hook on the bottom of this grid

full option grid with hooks