Symfony2 – Get action name from twig templates

January 27th, 2012 — 1:49pm

This is the easyest way that I have found to get the action name from a twig template:

 
{{ app.request.attributes.get('_template').get('name') }}

…and the controller object:

 
{{ app.request.attributes.get('_template').get('controller') }}

Comment » | snippet, Symfony2, twig

Unity first impressions

April 12th, 2011 — 8:54pm

I am developping a simple mini-game with Unity.

My first impression is that Unity is as powered as easy to use. I am really impresed.
I only I had better 3D Studio skills!

Woodman progress: http://nanomuelle.com/woodman
Unity home page: http://unity3d.com/

Comment » | Uncategorized

Owner Credential & Propel Admin Generator

December 3rd, 2010 — 1:11pm

It would be great to be able to specify in generator.yml a per object owner credential, so the generator only showed that actions to the owner of each object. Lets see how we can achieve this behavior easily.

Setting up the project.

Install and Configure Symfony

Follow the instructions of the great jobeet tutorial to install symfony and create the frontend application. We will use the propel ORM.

Instaling sfGuardPlugin

Following the symfony philosophy, we do not need to reinvent the wheel.

$ symfony plugin:install sfGuardPlugin

Preparing schema and some sample data

# config/schema.yml
propel:
  post:
    id:
    title:    { type: varchar(255), required: true }
    content:  { type: longvarchar, required: false }
    owner_id: 
      type: integer
      foreignTable: sf_guard_user
      foreignReference: id
      required: true
# data/fixtures/fixtures.yml
sfGuardUser:
  andrew:
    username: andrew
    password: andrew
  valentine:
    username: valentine
    password: valentine
 
Post:
  -
    title: Andrew's Post
    content: This is the content of the first Andrew's post.
    owner_id: andrew
  -
    title: Valentine's Post
    content: This is the content of the first Valentine's post.
    owner_id: valentine

Build models, load data and create the post module

$ symfony generate:app frontend
$ symfony propel:build --all --and-load
$ symfony propel:generate-admin frontend Post
$ symfony plugin:publish-assets
$ symfony clear cache

The funny work

At this point, if you go to the url ocag.localhost/post, we can see the list of posts, with two actions attached to each post: edit and delete.

What we want is that the admin-generator only shows the actions edit and/or delete to the owner of each post.

Enabling login module and securing post module

Add the following to settings.yml

# apps/frontend/settings.yml
# ...
all:
  .settings:
    # ...
    # Modules
    enabled_modules: [default, sfGuardAuth]
 
  .actions:
    # ...
    login_module:   sfGuardAuth
    login_action:   signin
    secure_module:  sfGuardAuth
    secure_action:  secure

Secure the post module

# apps/frontend/modules/post/config/security.yml
default:
  is_secure: true

Edit the generator.yml

First, we set the owner credential to an object action.

What we woult like the admin generator to do with this, is to hide that action to everybody but the creator of the object being procesed.

Saddly this is not way the default admin generator works, but we will soon see how to build our own customized admin theme with this behavior.

Don’t be afraid! It’s easyer than it appears.

# apps/modules/post/config/generator.yml
generator:
  class: sfPropelGenerator
  param:
    model_class:           Post
    theme:                 admin
    non_verbose_templates: true
    with_show:             false
    singular:              Post
    plural:                Posts
    route_prefix:          post
    with_propel_route:     1
    actions_base_class:    sfActions
 
    config:
      actions: ~
      fields:  ~
      list:
        #
        # setting owner credential
        #
        object_actions:
          _edit:
            credentials: owner
          _delete:
            credentials: owner
      filter:  ~
      form:    ~
      edit:    ~
      new:     ~

Creating myadmin theme

Create the folder data/generator/sfPropelModule/myadmin and copy the admin theme files in order to customize them:

$ mkdir -p data/generator/sfPropelModule/myadmin
$ cp -r lib/vendor/symfony/lib/plugins/sfPropelPlugin/data/generator/sfPropelModule/admin/* \
data/generator/sfPropelModule/myadmin

Configure the generator.yml to use myadmin theme

# apps/modules/post/config/generator.yml
generator:
  #...
  param:
    #...
    theme:                 myadmin

The partial-generator responsible of the object_actions is data/generator/sfPropelModule/myadmin/template/templates/_list_td_actions.php. We are going to add a line that calls a user function that dinamically adds or revoques the owner credential depending on the object being processed.

<?php echo '<?php $sf_user->addOwnerCredentials($' . $this->getSingularName() . ') ?>' ?>
<td>
  <ul class="sf_admin_td_actions">
<?php foreach ($this->configuration->getValue('list.object_actions') as $name => $params): ?>
<?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 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 endforeach; ?>
  </ul>
</td>

Now edit myUser.class.php to code the addOwnerCredentials method

// apps/frontend/lib/myUser.class.php
class myUser extends sfGuardSecurityUser
{
  protected static $OWNER_CREDENTIAL = 'owner';
 
  /**
   * If the user is the owner of the $object adds owner credentials,
   * otherwise remvoques it.
   */
  public function addOwnerCredentials($object)
  {
    if (!$this->isAuthenticated())
    {
      return false;
    }
 
    if (!$object)
    {
      return false;
    }
 
    if (get_class($object) == 'sfOutputEscaperObjectDecorator')
    {
      $object = $object->getRawValue();
    }
 
    if (method_exists($object, 'isOwner') && $object->isOwner($this->getGuardUser()))
    {
      $this->addCredential(self::$OWNER_CREDENTIAL);
    } else
    {
      $this->removeCredential(self::$OWNER_CREDENTIAL);
    }
  }
}

Tunning Post model

The only thing we have to do now is to add the isOnwer method to each model that we want to be checked against owner credential, Post model in our case. So lets edit lib/model/Post.php

// lib/model/Post.php
class Post extends BasePost {
  public function isOwner($sf_guard_user) {
    return $this->owner_id == $sf_guard_user->getId();
  }
}

Try it

And thats all. Clear caches, login as Andrew or Alice and see how the admin generator only shows the action links to the owner of each post. See the post list logged as Andrew:

Very immportant final comment

Remember that the only thing that we have done with this is to automatize the view layer generated by the admin generator. You still must securize the edit and delete actions of each module in order to avoid undeserable people to be able to execute them.

Comment » | admin-generator, php, propel, Symfony

How I beat Bejeweled Blitz

October 21st, 2010 — 6:36pm

Tired of being beated week after week by my facebook friends, I decided to investigate the most hidden secrets of Bewejeled Blitz. After one or two thousands games, I finally realize that the only secret was to be the fastest. As I was not the fastest, there was only one thing I could do to beat my friends… I would program a script that beat them for me.

Of course, it was an incredible opportunity to learn a little more about python also.

How the script works

Basically, the script do what a human do while playing:

  1. Look at the board. The script captures the Bejeweled Blitz board, and analyzes where the jewels are. To do so, I used the PIL library to capture the screen. After that, I posterize the image reducing the number of colors, making it easy to distinguish the jewels between each other.
  2. Find a goal. The script simulates a jeweled move in memory, and looks up for a goal (three-in-a-row). If there is a goal, the script moves the mouse (PyWin32 API) and actually makes the move.
  3. Repeat everything again and again until game-time ends.

How to use it

The first thing that you need is to find out the (x, y) coordinates of the Bejeweled Blitz Board. To do this, start a Bejewelled Blitz and capture the screen with the –save option of the script. The next command makes a capture of the screen and saves it with the name “my_capture.bmp”.

c:>python Bwbot.pyw --save my_capture.bmp 0 0

Then open the image with a program like GIMP (http://www.gimp.org/) and look up the upper left coordinates of the Bejewelled Blitz Board.

Bejeweled Blitz Board coordinates

To make the script play, just start a new game, and execute the script passing it the x and y coordinates you has just find out, for example:

c:>python Bwbot.pyw 527 455

The script

##
## Bwbot.pyw
##
## A python script to play Bejeweled Blitz
##
## Author:  Fernando García
## Licence: The script is under public domain. You can use, distribute or
##          modify it at your own risk.
##
import win32api, win32con
import random
import Image
import ImageChops
import math, operator
from PIL import ImageGrab
from PIL import ImageOps
import time
import argparse
 
def saveImage(filename):
    "Capture screen and save it to a bmp file"
 
    _im = ImageGrab.grab()
    _im.save(filename, "BMP")        
 
class Bwbot:    
    WIDTH = 320   # width of the board
    HEIGHT = 320  # height of the board
 
    COLS = 8      # number of cols of the board
    ROWS = 8      # number of rows of the board
    LAST_ROW_INDEX = 7 # index of the last row of the board
    LAST_COL_INDEX = 7 # index of the last row of the board
 
    SQUARE_WIDTH = WIDTH / COLS   # width of a grid square
    MID_SQUARE_WIDTH = SQUARE_WIDTH / 2 # mid width of a grid square
 
    SQUARE_HEIGHT = HEIGHT / ROWS   # heigth of a grid square
    MID_SQUARE_HEIGHT = SQUARE_HEIGHT / 2 # mid height of a grid square
 
    def __init__(self, ox, oy):
        "Initializes instance variables"
        #
        # play time in secconds
        #
        self.playtime = 55  
 
        #
        # drag delay in miliseconds
        # 20.-human 15.-optimal 10.-quick
        #
        self.drag_delay = 20
 
        #
        # screen position of the board in pixels
        #
        self.ox = ox        # x screen coordinate of the board
        self.oy = oy        # y screen coordinate of the board
 
        #
        # rect of screen to capture de board 
        #
        self.grid = (self.ox, self.oy, self.ox + Bwbot.WIDTH, self.oy + Bwbot.HEIGHT)
 
        #
        # Inner representation of the board
        #
        self.matrix = [[None for _col in xrange(Bwbot.COLS)] \
            for _row in xrange(Bwbot.ROWS)]
 
        #
        # Sample size. The size in pixels of the square used to sample colors
        # 
        self.sample_size = 8
 
    def toMouseCoordinates(self, pos):
        "Translates grid coordinates to mouse coordinates"
 
        _x = self.ox + pos[0] * Bwbot.SQUARE_WIDTH + Bwbot.MID_SQUARE_WIDTH
        _y = self.oy + pos[1] * Bwbot.SQUARE_HEIGHT + Bwbot.MID_SQUARE_HEIGHT
        return (_x, _y)
 
    def click(self, pos):
        "Make a mouse click. Pos especified in grid coordinates"
        _x, _y = self.toMouseCoordinates(pos)
        win32api.SetCursorPos((_x, _y))
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, _x, _y, 0, 0)
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, _x, _y, 0, 0)
 
    def dragJewel(self, pos1, pos2):
        "Drags a jewel from pos1 to pos2"
 
        self.click(pos1)
        self.click(pos2)
        self.wait(self.drag_delay)
 
    def wait(self, delay):
        "Do nothing during delay miliseconds"
 
        _delay = delay / 1000.0
        _ref = time.time()
        while (time.time() - _ref) < _delay:
          pass
 
    def captureBoard(self):
        "Capture and preproceses Bejeweled Blitz board"
 
        _im = ImageGrab.grab()                           # capture and
        _im = ImageOps.posterize(_im.crop(self.grid), 3) # reduce colors
        return _im
 
    def buildMatrix(self):
        "Build the matrix of colors representing the board"
 
        _im = self.captureBoard()
        for _row in range(Bwbot.ROWS):
            for _col in range(Bwbot.COLS):
                _cx = (_col * Bwbot.SQUARE_WIDTH) + Bwbot.MID_SQUARE_WIDTH
                _cy = (_row * Bwbot.SQUARE_HEIGHT) + Bwbot.MID_SQUARE_HEIGHT
                _rect = (_cx, _cy, _cx + self.sample_size, _cy + self.sample_size)
                _sample_im = _im.crop(_rect)
 
                # store only de predominant color
                self.matrix[_col][_row] = sorted(_sample_im.getcolors())[0][1]
 
    def evaluatePos(self, pos):
        "Returns True if matrix contains a goal in the specified pos"
 
        _col, _row = pos
        _val = self.matrix[_col][_row]
 
        _col_m1 = _col - 1 # col minus 1
        _col_m2 = _col - 2 # col minus 2
        _col_p1 = _col + 1 # col plus 1
        _col_p2 = _col + 2 # col plus 2
 
        _row_m1 = _row - 1 # row minus 1
        _row_m2 = _row - 2 # row minus 2
        _row_p1 = _row + 1 # row plus 1
        _row_p2 = _row + 2 # row plus 2
 
        # horizontal
        if _col_m1 >= 0 and _val == self.matrix[_col_m1][_row]:
          if _col_m2 >= 0 and _val == self.matrix[_col_m2][_row]:
            return True
          elif _col_p1 < Bwbot.COLS and _val == self.matrix[_col_p1][_row]:
            return True
        elif _col_p2 < Bwbot.COLS and _val == self.matrix[_col_p1][_row] \
            and _val == self.matrix[_col_p2][_row]:
          return True
 
        # vertical
        if _row_m1 >=0 and _val == self.matrix[_col][_row_m1]:
          if _row_m2 >= 0 and _val == self.matrix[_col][_row_m2]:
            return True
          elif _row_p1 < Bwbot.ROWS and _val == self.matrix[_col][_row_p1]:
            return True
        elif _row_p2 < Bwbot.ROWS and _val == self.matrix[_col][_row_p1] \
            and _val == self.matrix[_col][_row_p2]:
          return True
 
        # no goal                
        return False
 
    def swap(self, pos1, pos2):
        "Swap the values of pos1 and pos2 in the matrix"
 
        _aux = self.matrix[pos1[0]][pos1[1]]
        self.matrix[pos1[0]][pos1[1]] = self.matrix[pos2[0]][pos2[1]]
        self.matrix[pos2[0]][pos2[1]] = _aux
 
    def tryMove(self, pos1, pos2):
        "Simulates de move in the matrix and return True if there is a goal"
 
        self.swap(pos1, pos2)                                  # swap positions
        _goal = self.evaluatePos(pos1) or self.evaluatePos(pos2) # evaluate and
        self.swap(pos1, pos2)                                  # restore matrix
        return _goal
 
    def play(self):
        "plays the game during"
 
        _tnow = _tini = time.time()
        while(_tnow - _tini) < self.playtime:
            self.buildMatrix()
            for _row in xrange(Bwbot.ROWS):
                for _col in xrange(Bwbot.LAST_COL_INDEX):
                    # evaluating pos
                    _pos1 = (_col, _row)
 
                    # try swap right
                    _pos2 = (_col + 1, _row)
                    _goal = self.tryMove(_pos1, _pos2)
 
                    # if not goal, try swap down
                    if _row < Bwbot.LAST_ROW_INDEX and not _goal:
                        _pos2 = (_col, _row + 1)
                        _goal = self.tryMove(_pos1, _pos2)
 
                    # if goal, do the move 
                    if _goal:
                        self.dragJewel(_pos1, _pos2)
 
            # time elapsed
            _tnow = time.time()
 
if __name__ == '__main__':
 
    parser = argparse.ArgumentParser(description="Bejeweled Blitz Bot.")
    parser.add_argument('x', type=int, nargs=1, \
        help='xpos of the Bejeweled Blitz board')
    parser.add_argument('y', type=int, nargs=1, \
        help='ypos of the Bejeweled Blitz board')
    parser.add_argument('--save', nargs=1, required=False, \
        help='capture de screen and saves it to a file')
    args = parser.parse_args()
 
    if args.save:        
        saveImage(args.save[0])
    else:
        bwb = Bwbot(args.x[0], args.y[0])
        bwb.play()

Requisites and Limitations

Future improvements and ToDo List

My only intention was to beat my friends, something that I successfully did. My hi-score is now over 1.000.000 points, much more than any human player could even dream. But the script is far to be perfect. It lacks of a more efficient evaluation routine that selects the best move at each moment, choosing a 5-in-a-row move before the others for example. Another important defect is its inability to detect multipliers and other special jewels.

I am not planning to improve the script, but if you want to improve it, I will appreciate that you email me, just to know how the script evolves.

Enjoy!

Comment » | python

Snippet: Recursively Remove .svn Directories in Linux

January 29th, 2010 — 12:24am

Just a couple of snippets found in the net to make my life easier.
In the first one beware of the grave accent quotes.

rm -rf `find . -type d -name .svn`

or someone else proposed

find ./ -name .svn | xargs rm -fr

Comment » | Linux, snippet

Snippet: PHP Calling static class methods

December 1st, 2009 — 11:57am

Given the example class foo:

class foo {
  public static function myFunc() {
    return "foo - myFunc";
  }
}

The class name is known in the calling context:

  echo foo::myFunc();

The class name is stored in a php var:

  $class_name = "foo";
 
  /** PHP 5.2.x */
  echo call_user_func(array($class_name, "myFunc"));  
 
  /** PHP 5.3.x */
  echo $class_name::myFunc();

More info on call_user_func: http://es.php.net/manual/en/function.call-user-func.php
More info on static keyword: http://es.php.net/manual/en/language.oop5.static.php

Comment » | php

Snippet: sfWidgetFormJQueryDate for Spanish culture

November 16th, 2009 — 11:47am
  1. Be sure that sfFormExtraPlugin is installed:

    $ php symfony plugin:install sfFormExtraPlugin
  2. In the template:

    <?php use_javascript('ui/i18n/jquery-ui-i18n.js') ?>
  3. In the filter or form:

    $this->widgetSchema['my_date_field'] = new sfWidgetFormJQueryDate(array(
          'culture' => 'es',
          'format'  => '%day%/%month%/%year%', 
          'config'  => '{"showMonthAfterYear": false, "firstDay": 1 }'
        ));

More info on ‘config’ option in http://jqueryui.com/demos/datepicker.

Comment » | Symfony

Symfony Partials: Accessing Raw Data defined in an Action

October 15th, 2009 — 10:54am

Sometimes we need to pass some message from the action to a partial via template. If this message only contains plain text there is no problem, we simply define the property in the action, and in the template we pass it to the partial, like this:

In the action:

// modules/mymodule/actions/actions.class.php
class mymoduleActions extends sfActions
{
  public function executeShow(sfWebRequest $request)
  {
    $this->msg = "My plain info message";
  }
}

In the template:

<!-- modules/mymodule/templates/showSuccess.php -->
<hr />
<h1>My Module</h1>
<?php include_partial('mypartial', array('msg' => $msg)); ?>
<hr />

And in the partial:

<!-- modules/mymodule/templates/_mypartial.php -->
<p><? echo $msg ?></p>

The problem comes when we include a little of html in the message, for example, lets change the action:

class mymoduleActions extends sfActions
{
  public function executeShow(sfWebRequest $request) 
  {
    $this->msg = "My <b>html</b> info message";
  }
}

Now, if we leave the template and the partial unchanged, you get this in the navigator:


My Module

My &lt;b&gt;html&lt;/b&gt; info message


Ok, it seems that we forgot to get raw data, so lets change the template:

<!-- modules/mymodule/templates/showSuccess.php -->
<hr />
<h1>My Module</h1>
<?php include_partial('mypartial', array('msg' => $sf_data->getRaw($msg))) ?>
<hr />

And now the result is:


My Module

My <b>html</b> info message


That is a little better, but it is not still the desired result. To get it, we have to also change the partial:

<!-- modules/mymodule/templates/_mypartial.php -->
<p><? echo $sf_data->getRaw($msg) ?></p>

And finally we get what we wanted:


My Module

My html info message


The conclusion is that if we want to send raw data from the action to the partial, we will have to raw it in both, the template and the partial.

3 comments » | Symfony

Easy configuring timezone in web server

September 18th, 2009 — 12:36pm

Simply Include the next line in the .htaccess file of your web server:

php_value date.timezone Europe/Madrid

Here you can see the complete List of Supported Timezones.

Comment » | snippet, tips

Setting a value to a hidden form symfony field

July 16th, 2009 — 10:23am

The case

Sometimes, when creating a new object, we need to give a default value to a specific field but we don’t want this field to be visible in the form.
A tipical example would it be a blog -> post relationship. Where we want the blog_id field to be set automatically but we want it to be hidden.

Blog->Post

Blog->Post

The mistake

When creating a new post for a blog, we do not want the blog_id field to be visible, so we typically go to PostForm.class.php and unset the field blog_id:

//myproject/lib/form/PostForm.class.form
class PostForm extends BasePostForm
{
  public function configure()
  {
    unset( $this['blog_id'] );
  }
}

Then, we edit the action class and set a default value for the blog_id field:

// mypoject/modules/post/actions/actions.class.php
class postActions extends sfActions
{
  public function executeNew(sfWebRequest $request)
  {  
    // create the form
    $form = new PostForm();
 
    // get blog_id parameter from the request
    // and set the default value
    $form->setDefault("blog_id", $request->getParameter('blog_id') );
 
    $this->form = $form;
  }
  //... rest of actions here ...
}

Finally, when we try to create a new post, we realize that the blog_id has not been set correctly.

The solution

Instead of unsetting the blog_id field, we have to change its widget to a sfWidgetFormInputHidden:

//PostForm.class.form
class PostForm extends BasePostForm
{
  public function configure()
  {
    // the wrong way in this case
    // unset( $this['blog_id'] );
 
    // the right way in this case
    $this->widgetSchema['blog_id'] = new sfWidgetFormInputHidden();
  }
}

This way the field blog_id remains hidden but do exists in the form, so the setDefault method works correctly.

8 comments » | Symfony

Back to top