Skip to content

jonocode – a developer's blog

I moved! –> http://pressreload.com

Category Archives: PHP

Symfony has automated the process of creating a CRUD application, and even has built-in CSRF protection.

However, for a site with multiple users, the generated CRUD code just won’t do.

In my schema, each row of a model is owned by a user, according to their user_id. In the dashboard application I am building, each user has a list of programs.

They can create, read, update and delete each program that they own.

But the generated actions would allow users access to other user’s programs, simply by changing the id in URLs such as /program/edit/id/1.

My solution? Store the user_id of the currently logged in user in myUser.class.php. Since the myUser class has ownership of the id of the currently logged in user, I thought it was appropriate to implement convenience getters and setters for the dashboard here.

So my list action would contain:

$this->programs = $this->getUser()->getPrograms();

The myUser::getPrograms() method is implemented like so:

public function getPrograms()
{
  return Doctrine::getTable('Program')->fetchByOwner($this->getId());
}

public function getProgram($pk)
{
  $program = Doctrine::getTable('Program')->find($pk);
  return $this->hasOwnershipOf($program) ? $program : false;
}

public function hasOwnershipOf($row)
{
  if ( isset($row['user_id']) )
  {
    if ( $row['user_id'] == $this->getId() )
    {
      return true;
    }
  }
  return false;
}

Adding the myUser::fetchByOwner() method leaves the model functionality open to being called from another administrative application other than the dashboard. You could use a Doctrine magic method here, however I would prefer to return programs in alphabetical order (or perhaps allow for other options to be passed in using a Doctrine_Query as a second parameter).

For myUser::getProgram($pk), I simply use the ::find() magic method in doctrine to retrieve the object by primary key, as usual, and then return it only if it matches the user_id of the logged-in user.

Writing for multiple users is common place for web-based applications. However, it’s something I don’t usually deal with at work where I write simple, single-user back ends for editing website content.

Is there a more elegant way of handling user ownership of database content? Perhaps by writing a doctrine behavior? How do you usually go about it?

Please comment.

I’m sure there are a number of ways you can handle this. There are also tons of modifications you can do to make it more useful, or better adapted to your particular needs. This post will quickly illustrate one way to create a dynamic stylesheet in Symfony 1.2.

I just finished writing a module that allows my users to change their background color. So I thought I’d share a similar way of doing this with an example.

Create a new doctrine model. Since we’re keeping things simple, our style sheet will only have one editable element: the body tag’s background-image.

DynamicCSS:
  columns:
    identifier: string(255)
    background_color: string(255)

Add a fixture and load it up:

/data/fixtures/fixtures.yml

DynamicCSS:
  black:
    identifier: "main"
    background_color: "#000000"
$ symfony doctrine:build-all-reload

The identifier will be the stylesheet’s name. You’ll see it in the route, which you’ll add now:

/apps/frontend/config/routing.yml

dynamiccss:
  url: /dynamiccss/:identifier.css
  param: { module: dynamiccss, action: render }

Create a new module:

$ symfony init-module frontend dynamiccss

This module will be used to render a dynamic style sheet. You’ll want to make sure it doesn’t render the layout, and that it returns a content type of ‘text/css’. So edit the actions file and add a preExecute() method. Of course, you’ll also need a method to fetch the style sheet and render it.

/apps/frontend/modules/dynamiccss/actions/actions.class.php

  
...
  public function preExecute()
  {
    $this->setLayout(false);
    $this->getResponse()->setContentType('text/css');
  }

  public function executeRender()
  {
    $this->stylesheet = Doctrine::getTable('DynamicCSS')
      ->getByIdentifier($request->getParameter('identifier'));
    $this->forward404Unless($this->stylesheet);
  }
...

Next we’ll add the template, which is at this point is very simplistic. You’ll need to have that ::getByIdentifier() method implemented in the DynamicCSSTable class, as well.

/apps/frontend/modules/dynamiccss/templates/renderSuccess.php

body {
  background-color: <?php echo $stylesheet->getBackgroundColor() ?>;
}

/lib/model/doctrine/DynamicCSSTable.class.php

...
public static function getByIdentifier($identifier)
{
  return $this->createQuery('c')
    ->addWhere('c.identifier = ?', $identifier)->fetchOne();
}
...

Now you can add your dynamic style sheet to the view. Here is an example:

/apps/frontend/config/view.yml

default:
  ...
  stylesheets: [/dynamiccss/main.css: { raw_name: true }]
  ...

That “raw_name” option allows you to override the typical way of creating an asset’s file name.

Now whenever someone edits the background color for the “main” style sheet, from the database, the background color can change.

You’ll still need to add an interface for editing the background color (or however many other options you add). That is up to you.

Complaints? Pointers? Thought of a better way?

Please post.

Tags: ,

This post assumes you have some experience with Symfony 1.0.

At my work I needed to create several different versions of the same site. This mainly entailed changing background images to get a slightly different look and feel, and changing the content category, depending on a request parameter.

For this tutorial, I will use the company I work for as an example. Momentum IT Group has two distinct divisions: ELearning and Web. So let’s say I wanted to make slight design changes depending on which division I’m looking at.

We’ll start where all things Symfony begin: with the routes. Open up apps/frontend/config/routing.yml and enter the following:

division_page:
  url:               /:division/:slug
  param:          { module: cms, action: display }
  requirements: { division: elearning|web }

I leave the cms module for you to implement. The display action would take a unique slug as a key to look up the page content from the database for display.

In this route, the division is required to be either elearning or web, and these two options will be used to display a different background design (and possibly select from a different content category in your cms module).

The next step is to implement the switch which determines which background to apply, if any. You can always write this code in your cms module, but you may have other modules other than the cms module which need to change design elements depending on which division the user is viewing. To handle this situation, you can create a filter.

A filter is applied once for each request. Start by opening apps/frontend/config/filters.yml and adding a new filter class:

rendering:    ~
web_debug: ~
security:     ~

# generally, you will want to insert your own filters here
cssFilter:
  class:       cssFilter

cache:       ~
common:    ~
flash:         ~
execution:  ~

Create the filter class in apps/frontend/lib/cssFilter.class.php:

<?php

class cssFilter extends sfFilter
{
  public function execute($filterChain)
  {
    if ( $this->isFirstCall())
    {
      $response = $this->getContext()->getResponse();
      switch ( sfContext::getInstance()->getRequest()->getParameter('division') ) 
      {
        case 'elearning':
          $response->addStylesheet('elearning', 'last');
        break;
        case 'web':
          $response->addStylesheet('web', 'last');
        break;
      }
    }
  }
}

Make sure you place two new CSS files in your web/css directory.

For this example, they are elearning.css and web.css. They will be the last css files in your HTML header and will override your main.css where needed. So, you might override a background image like so:

web/css/main.css

body {
  background: url(../images/background.jpg) no-repeat top left;
}

web/css/elearning.css

body {
  background-image: url(../images/elearning.jpg);
}

Now if you direct your browser to [your_site]/elearning/[page_slug], you will have taken advantage of CSS cascading and Symfony filters to present a slightly different design depending on the context.

Tags: , , ,

This post has moved to:

http://www.pressreload.com/adding-sfdoctrineguardplugin

Please update your links.

So you’ve managed to integrate FCKEditor into Symfony for use with your administrative console.

Problem: FCKEditor has a file uploader for media files.  Images, documents, Flash files, that sort of thing.  It’s run off a PHP connector.

All that a hacker needs to do is enter a URL like so:

/js/fckeditor/editor/filemanager/browser/default/browser.html?Type=Image&amp;Connector=../../connectors/php/connector.php

…and he or she is able to upload media files.

You’ve secured your application using security.yml, and perhaps the sfGuard plugin.  But what about that file uploader?  It isn’t launched by one of your controllers.  It has its own.

Solution: Turn the PHP connector in FCKEditor into a controller.

1.  Open up the PHP connector’s config.php at:

web/js/fckeditor/editor/filemanager/connectors/php/config.php

2.  Turn it into a Symfony controller.  Insert the following at the top of the file:

define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/../../../../../../..'));
define('SF_APP',         'backend');
define('SF_ENVIRONMENT', 'prod');
define('SF_DEBUG',       false);

require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');

if ( !sfContext::getInstance()-&gt;getUser()-&gt;isAuthenticated() ) {
  exit();
}

Make sure you enter the correct SF_APP, and a relative path to SF_ROOT_DIR.

If the user isn’t authenticated, I simply exit.  The reason I don’t redirect to a nice “session timed out” page is because the PHP is called from FCKEditor’s Javascript.  If anyone has a more elegant solution, please comment!

Tags: , , ,

While using Eclipse PDT, every time I tried to open a YAML file, the software would try to open the file externally.  Since I had no YAML editor installed, this would generate an error.

Now I could right click and choose Open With –> Text Editor.  But I wanted something a little better than a simple text editor, and I didn’t want the extra clicks.

That’s where Yedit came in, another great plugin for Eclipse.  The plugin can be found here:

http://dadacoalition.org/yedit

It installs easily in Eclipse:

Help –> Software Updates –> Add Site

Enter:

http://dadacoalition.org/yedit

Select the plugin from the list under this site, and click Install…

Remember to restart Eclipse after you install, in order to see the update.  You should now be able to open YAML files in your project easily.

Tags: , , , ,