![]() |
|
Snippets |
|
Recently I needed to use the xml-rpc lib with symfony. I created a function to make cache of xml-rpc requests using the symfony's sfFileCache and it work good.
I'm using the xml-rpc library http://phpxmlrpc.sourceforge.net/
public function callCache( $methodName, $params ){ // generated a key to cache file $key = $methodName."_".implode('_', $params->getval() ); $cache = new sfFileCache( array( 'cache_dir' => sfConfig::get('sf_cache_dir')."/xmlrpc/" ) ); // check if already exist the cache file if( ! $cache->has( $key ) ){ //init xml-rpc message/ $f = new xmlrpcmsg($methodName, $params); $f->addParam($params); $c = new xmlrpc_client("/xml-rpc", $this->host, 443, 'https' ); $c->setCredentials($this->username, $this->password); $c->setDebug( $this->debug ); $response = $c->send($f); $cache->set( $key , serialize( $response ), 3600); }else{ $response = $cache->get( $key ); $response = unserialize( $response ); } if( ! $response->value() ){ $this->erro( $response->errno, $response->faultString() ); } $results = php_xmlrpc_decode( $response->value() ); $this->rows = count($results); $this->data = $results; }
I spent some time trying to figure out how to make sure that one checkbox was checked before saving a form in symfony 1.2.
I read about Global Validators (http://www.symfony-project.org/forms/1_2/en/02-Form-Validation#chapter_02_global_validators) but I couldn't find anything to fit my needs. Finally, after looking at this snipped: http://www.symfony-project.org/cookbook/1_2/en/conditional-validator , I created the validation I needed, here is the snipped to help someone in the same situation:
class SampleForm extends BaseSampleForm { public function configure() { // add a post validator $this->validatorSchema->setPostValidator( new sfValidatorCallback(array('callback' => array($this, 'checkAtLeastOne'))) ); unset( $this['created_at'], $this['updated_at'] ); } public function checkAtLeastOne($validator, $values) { if (!$values['sunday'] && !$values['monday'] && !$values['tuesday'] && !$values['wednesday'] && !$values['thursday'] && !$values['friday'] && !$values['saturday']) { // no checkbox was checked, throw an error throw new sfValidatorError($validator, 'Check at least one day of the week'); } // at least one checkbox is checked, return the clean values return $values; } }
This snippet allows you to evaluate functions speed and display them in symfony debug bar.
$timer = sfTimerManager::getTimer ('ldap_request'); // Create and start a new timer // do some stuff $timer->addTime (); // stop and store the timer value
Environnement : Symfony 1.2 - sfDoctrinePlugin
Context : doctrine:generate-admin modules
Problème : filters case-sensitive
Objective : make filters case-unsensitive
Every classes inherited from sfFormFilter alse inherite from BaseFormFilter, which is modifyable in every project. In the Doctrine case, sfFormFilterDoctrine and BaseFormFilterDoctrine. This class "Base" is here :
lib/filter/doctrine/BaseFormFilterDoctrine.class.php
The aimed method is the one which works on text fields. The line we want is the line 209 of the file symfony/lib/plugins/sfDoctrinePlugin/lib/form/sfFormFilterDoctrine.class.php :
$query->addWhere('r.' . $fieldName . ' LIKE ?', '%' . $values['text'] . '%');
So a solution should be the override this method replacing LIKE by ILIKE. Let's try :
Overriding the class sfFormFilterDoctrine's method addTextQuery() should give you this kind of stuff if there is no more work done on it :
# lib/filter/doctrine/BaseFormFilterDoctrine.class.php /** * Project filter form base class. * * @packagefilters * * @versionSVN: $Id: sfDoctrineFormFilterBaseTemplate.php 11675 2008-09-19 15:21:38Z fabien $ */ abstract class BaseFormFilterDoctrine extends sfFormFilterDoctrine { public function setup() { } // totally inspirated from lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/lib/form/sfFormFilterDoctrine.class.php // function addTextQuery() // line modified : 209 // be careful on symfony updates to update this method if there are changes protected function addTextQuery(Doctrine_Query $query, $field, $values) { $fieldName = $this->getFieldName($field); if (is_array($values) && isset($values['is_empty']) && $values['is_empty']) { $query->addWhere('r.' . $fieldName . ' IS NULL'); } else if (is_array($values) && isset($values['text']) && != $values['text']) { // Here is the change, with "ILIKE" instead of "LIKE" $query->addWhere('r.' . $fieldName . ' ILIKE ?', '%' . $values['text'] . '%'); } } }
The original documentation in french is available here :
http://www.e-glop.net/main/Filtres_case-unsensitive_dans_Symfony
This is a class for symfony 1.2 for images. It extends the class sfValidatorFile.
<?php /** * sfValidatorFileImage * * Original validator for symfony 1.0 : http://snippets.symfony-project.org/snippet/259 * * @package symfony * @subpackage validator * @author Yoann Brieux <yoann |dot¤ brieux #at] gmail ~dot} com> * @version 0.1 */ class sfValidatorFileImage extends sfValidatorFile { /** * @param array $options An array of options * @param array $messages An array of error messages * * @see sfValidatorFile */ protected function configure($options = array(), $messages = array()) { parent::configure($options, $messages); $this->addMessage('invalid_image', '%value% is an incorrect image file.'); $this->addMessage('max_height', '"%value%" height is too long (%max_height% pixels max).'); $this->addMessage('min_height', '"%value%" height is too short (%min_height% pixels min).'); $this->addMessage('max_width', '"%value%" width is too long (%max_width% pixels max).'); $this->addMessage('min_width', '"%value%" width is too short (%min_width% pixels min).'); $this->addOption('max_height'); $this->addOption('min_height'); $this->addOption('max_width'); $this->addOption('min_width'); $this->addOption('is_only_image',false); } /** * @see sfValidatorFile */ protected function doClean($value) { $clean = parent::doClean($value); $size = @getimagesize($clean->getTempName()); if (!$size && !$this->getOption('is_only_image')) { return $clean; } if (!$size){ throw new sfValidatorError($this, 'invalid_image', array('value' => $value['name'])); } list($width, $height) = $size; if($this->getOption('max_height') < $height){ throw new sfValidatorError($this, 'max_height', array('value' => $value['name'], 'max_height' => $this->getOption('max_height'))); } if($this->getOption('min_height') > $height){ throw new sfValidatorError($this, 'min_height', array('value' => $value['name'], 'min_height' => $this->getOption('min_height'))); } if($this->getOption('max_width') < $width){ throw new sfValidatorError($this, 'max_width', array('value' => $value['name'], 'max_width' => $this->getOption('max_width'))); } if($this->getOption('min_width') > $width){ throw new sfValidatorError($this, 'min_width', array('value' => $value['name'], 'min_width' => $this->getOption('min_width'))); } return $clean; } }
In the your form library :
$this->validatorSchema['field'] = new sfValidatorFileImage(array( 'required' => false, 'path' => sfConfig::get('sf_upload_dir').'/news/', 'max_height' => 238, 'min_height' => 238, 'max_width' => 270, 'min_width' => 270, 'mime_types' => array('image/jpeg','image/pjpeg','image/png','image/x-png','image/gif','application/x-shockwave-flash') ) );
If the variable "is_only_image" is true and function "getimagesize" return false (is not a valid image for example), this validator cause an sfValidatorError exception with the message "[your file] is an incorrect image file."
A piece of code that will extract the existing labels from a form into a xliff file. It will only pick out the labels that have been set by the widgetForm->setLabel() command.
example for UserForm.class.php you can extract it using:
php symfony i18n:extract-form user
.
<?php class i18nExtractFormTask extends sfBaseTask { protected function configure() { // // add your own arguments here // $this->addArguments(array( // new sfCommandArgument('my_arg', sfCommandArgument::REQUIRED, 'My argument'), // )); $this->addOptions(array( new sfCommandOption('application', null, sfCommandOption::PARAMETER_REQUIRED, 'The application name', 'frontend'), new sfCommandOption('env', null, sfCommandOption::PARAMETER_REQUIRED, 'The environment', 'dev'), new sfCommandOption('connection', null, sfCommandOption::PARAMETER_REQUIRED, 'The connection name', 'default'), new sfCommandOption('source', null, sfCommandOption::PARAMETER_REQUIRED, 'Source language', 'en'), // add your own options here )); $this->addArgument('form', sfCommandOption::PARAMETER_REQUIRED, 'Name for the form to translate'); $this->namespace = 'i18n'; $this->name = 'extract-form'; $this->briefDescription = 'Extracts the labels from a form'; $this->detailedDescription = <<<EOF The [i18n-extract-form|INFO] task extracts the labels from a form class into a XLIFF file. Call it with: for CoolForm.class.php you can extract it using: [php symfony i18n-extract-form cool|INFO] EOF; } protected function execute($arguments = array(), $options = array()) { // initialize the database connection $databaseManager = new sfDatabaseManager($this->configuration); $connection = $databaseManager->getDatabase($options['connection'] ? $options['connection'] : null)->getConnection(); $sFormname = ucfirst($arguments['form']).'Form'; $oForm = new $sFormname(false); $labels = $oForm->getWidgetSchema()->getLabels(); $sfMessageSource = new sfMessageSource_XLIFF(sfConfig::get('sf_app_i18n_dir')); $sfMessageSource->setCulture($options['source']); $sfMessageSource->load($sFormname); $existingMessages = $sfMessageSource->read(); $aKeys = array_keys($existingMessages); $aMessages = array_keys($existingMessages[$aKeys[0]]); $unLabeled = array(); foreach ($labels as $name => $value) { if (!is_null($value)) { if (!in_array($value,$aMessages)) { $sfMessageSource->append($value); $this->logSection('i18n', "Adding: " . $value); } } else { $unLabeled[] = $name; } } // an extra service: generate code for the not yet labeled parts: still a bit buggy $unLabeled = array_diff($unLabeled, array("_csrf_token")); $unLabeled = array_values($unLabeled); $return = "Code for unlabeled fields.:\n"; foreach ($unLabeled as $label) { $return .= "\t\"" . '$this->widgetSchema->setLabel("' . $label .'","")'."\n"; } } }
People using Propel who want to perform custom joins and especially hydrations probably all faced the limited functionality that Propel offers for this: the probably well known doSelectJoinAll and doSelectJoinXXX methods (and similar for the count functionality)
Whenever you want to join only two related tables (or more, but not all), a related-related table (or even deeper) or a table that is related by multiple foreign-keys (like created_by and updated_by both refering to a user) you will find it gets hard to do this with the default functionality offered by Propel.
I have written a Helper/Plugin that solves this limitation, that can be downloaded from the plugin-pages: http://www.symfony-project.org/plugins/sfPropelHelperPlugin
The plugin contains:
The plugin does not change the way how to work with Propel, it will only expand the possibilities while remaining completely backwards compatible.
An example of how to use this helper:
Imagine you have a class of albums, and that every album can contain photos as well as other albums. You request all photo's sorted reversed on album name and join the related table to get the table-name in one go.
propel:
photo:
id:
album_id:
name: varchar(50)
album:
id:
album_id:
name: varchar(50)
php code:
$this->getContext()->getConfiguration()->loadHelpers('sfPropelPropertyPath'); $criteria = new Criteria(); $sortColumn = AlbumPeer::alias(str_replace('.', '_', 'Photo.Album') , AlbumPeer::NAME); // this should be done nicer some day $criteria->addDescendingOrderByColumn($sortColumn); $objectPaths = array('Photo', 'Photo.Album'); // this can be reduced (is similar) to array('Photo.Album'); $criteria = addJoinsAndSelectColumns($criteria, $objectPaths); $photos = hydrate($criteria, $objectPaths, $connection = null); // I haven't decided if I want hydrate to perform the joining as well foreach ($photos as $photo) { echo $photo->getAlbum()->getName()." -> ".$photo->getName()."<br>\n"; }
The resulting sql is:
SELECT Photo.ID, Photo.ALBUM_ID, Photo.NAME, Photo_Album.ID, Photo_Album.ALBUM_ID, Photo_Album.NAME FROM `photo` `Photo` LEFT JOIN album Photo_Album ON (Photo.ALBUM_ID=Photo_Album.ID) ORDER BY Photo_Album.NAME DESC
I designed so called ObjectPaths and PropertyPaths together with a colleague of mine (Frans van der Lek), to make it easier to define classes and respectively properties of the (related) classes. The Object Paths contain the name of the base-class, followed by the relation-names defined in the peer-classes. These relation-names are defined by the builder, but can be extended/modified by extending the basePeer class (getRelations)
You can extend your Classes with custom Get-methods that can be accessed with the property-paths. I will show how this all comes to use, in my next article about the sfDataSourcePlugin, that I will release soon. (A plugin that provides a generic interface to select, sort, filter and iterate-over rows, no matter if they are provided by Propel, Doctrine, an Array, an Imap connection, etc. The Datasource interface is very light (in contradiction to dbFinder (no offense)) which makes it easy to write your own implementation)
Please provide feedback, questions and if desired I can add more examples/info.
For those of us who just need a bit more security out of this already awesome authentication plugin. This has been tested to work on symfony 1.2 using sfGuardPlugin version 3.1.3 stable.
The default behavior of this snippet is a log that tracks each login attempt, the IP, the username entered, and whether it was successful or not. The brute force protection's default settings will check to see if more than 5 attempts for the same username in the past 2 minutes were failed attempts. It also includes a cooldown period of 15 minutes from the last failed login attempt. All of these variables can be changed in the arguments for checkBruteForce().
The first thing we need to do is create a log for sfGuard. Do this by adding the following to the sfGuard schema and then rebuilding your models, forms, and filters:
plugins/sfGuardPlugin/config/schema.yml:
sf_guard_log:
_attributes: { phpName: sfGuardLog }
id: ~
created_at: ~
ip: { type: varchar }
username: { type: varchar }
valid_login: { type: boolean }
Next, we need to extend the peer class for this new sfGuardLog model to handle brute force protection.
plugins/sfGuardPlugin/lib/mode/sfGuardLoginPeer.php:
/* Auther: Christopher Lewis (chris@bluehousegroup.com) Check for brute force login attempts Case I 1. Get all logins for this username in the past N minutes 2. If P or more of them are fails, this is likely a brute force attempt, return true Case II 1. Get last P logins 2. Are they all fails? AND Are they all within the same window of N minutes? AND Was the last attempt within the past Q minutes? If all are true, return true Where: N = $attempt_window P = $allowed_fail_count Q = $cooldown_window */ public static function checkBruteForce($username, $attempt_window = 2, $cooldown_window = 15, $allowed_fail_count = 5) { //Case I... $N_minutes_ago_timestamp = strtotime("$attempt_window minutes ago"); $N_minutes_ago_sqltime = date('Y-m-d H:i:s', $N_minutes_ago_timestamp); $c = new Criteria(); $c->add(sfGuardLogPeer::USERNAME, $username); $c->add(sfGuardLogPeer::CREATED_AT, $N_minutes_ago_sqltime, Criteria::GREATER_THAN); $entries = sfGuardLogPeer::doSelect($c); $failed_count = 0; foreach($entries as $log) { if($log->getValidLogin() == false) { $failed_count++; } } if($failed_count >= $allowed_fail_count) { return true; } //Case II... $all_fails = false; $all_in_window = false; $last_attempt_recent = false; //Are they all fails? $c = new Criteria(); $c->add(sfGuardLogPeer::USERNAME, $username); $c->addDescendingOrderByColumn(sfGuardLogPeer::ID); $c->setLimit($allowed_fail_count); $failed_count = 0; $timestamps = array(); $entries = sfGuardLogPeer::doSelect($c); foreach($entries as $pos => $log) { if($log->getValidLogin() == false) { $failed_count++; } array_push($timestamps, strtotime($log->getCreatedAt())); } if($failed_count >= $allowed_fail_count) { $all_fails = true; } else { return false; } //Are they all within the same window of N minutes? sort($timestamps); $time_a = $timestamps[0]; $time_b = $timestamps[(count($timestamps) - 1)]; $window_in_secs = $time_b - $time_a; $window_in_mins = $window_in_secs / 60; if($window_in_mins <= $attempt_window) { $all_in_window = true; } else { return false; } //Was the last attempt less than Q minutes ago? $Q_minutes_ago_timestamp = strtotime("$allowed_fail_count minutes ago"); $last_attempt_timestamp = strtotime($entries[0]->getCreatedAt()); if($Q_minutes_ago_timestamp <= $last_attempt_timestamp) { $last_attempt_recent = true; } else { return false; } //If all are true, return true if($all_fails == true && $all_in_window == true && $last_attempt_recent == true) { return true; } return false; }
Last, we'll need to overload the executeSignin action in the sfGuardAuth module to include our new logging and security features:
plugins/sfGuardPlugin/modules/sfGuardAuth/actions.class.php
public function executeSignin($request) { $user = $this->getUser(); if($user->isAuthenticated()) { return $this->redirect('@homepage'); } $class = sfConfig::get('app_sf_guard_plugin_signin_form', 'sfGuardFormSignin'); $this->form = new $class(); if($request->isMethod('post')) { $this->form->bind($request->getParameter('signin')); $values = $this->form->getValues(); //Log attempts $log = new sfGuardLog(); $log->setIp($_SERVER['REMOTE_ADDR']); $log->setUsername($request->getParameter('signin[username]')); //Check for brute force attempt $is_brute_force = sfGuardLogPeer::checkBruteForce($request->getParameter('signin[username]')); if($this->form->isValid() && !($is_brute_force)) { $this->getUser()->signin($values['user'], array_key_exists('remember', $values) ? $values['remember'] : false); //Used to disable concurrent logins $this->getUser()->getGuardUser()->setSession(session_id()); $this->getUser()->getGuardUser()->save(); // always redirect to a URL set in app.yml // or to the referer // or to the homepage $signinUrl = sfConfig::get('app_sf_guard_plugin_success_signin_url', $user->getReferer('@homepage')); $log->setValidLogin(true); $log->save(); return $this->redirect($signinUrl); } else { $log->setValidLogin(false); $log->save(); } } }
This works for symfony 1.2 and might be backwards compatible with 1.1 and 1.0, but I haven't tested it with those versions.
Place this file in an appropriate directory (eg. apps/myapp/lib/routing):
sfRegexExtendedRoute.class.php:
<?php class sfRegexExtendedRoute extends sfRoute { protected function compile() { $return = parent::compile(); // Case-insensitive matching (default symfony behaviour = NOT case-insensitive). if (isset($this->options['case_sensitive']) && !$this->options['case_sensitive']) { // Make sure $this->regex is in the form "#...#[???]" // and that [???] does not already include "i" (the case-insensitive flag). if (($parts = explode('#', strrev($this->regex))) && 3 <= ($num_parts = count($parts)) && '' == $parts[$num_parts-1] && false === strpos($parts[0], 'i')) { $this->regex .= 'i'; } } return $return; } }
Add the option case_sensitive: false in the route you want to make case-insensitive and set its class to sfRegexExtendedRoute:
apps/myapp/config/routing.yml:
...
my_route:
url: /my/case/insensitive/url/:whatever
options: { case_sensitive: false }
class: sfRegexExtendedRoute
...
Clear your cache:
> php symfony cc
Your url matching for that route should now be case-insensitive.
Changing case_sensitive: false to case_sensitive: true in routing.yml will revert the matching for that route back to (symfony default) case-sensitive matching.
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