![]() |
|
Snippets |
|
This simple filter automatically adds google analytics code for all pages. It's no longer required to modify all the layouts, just put google code to app.yml and this filter does everything for you.
Create a lib/filters/gaFilter.class.php file:
<?php class gaFilter extends sfFilter { public function execute($filterChain) { // Nothing to do before the action $filterChain->execute(); // Find google code and check if current module is not disabled if(($gaCode = sfConfig::get("app_ga_code",false)) !== false && !in_array($this->getContext()->getModuleName(),sfConfig::get("app_ga_disabled_modules",array()))) { //Decorate the response with the tracker code $response = $this->getContext()->getResponse(); $response->setContent(str_ireplace('</body>', $gaCode.'</body>',$response->getContent())); } } }
Add the filter to filters.yml :
# insert your own filters here # Google analytics filter ga_filter: class: gaFilter
Now you are able to insert GA code into your page. Simply copy the whole code and add it as ga_code option into your app.yml file:
all:
...
ga:
code: >
<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("...");
pageTracker._trackPageview();
} catch(err) {}</script>
It works on fact, that you probably will not have a </body> tag in other place except at the end of your page and you will not use it in other content type than text/html.
Optionally you may disable GA filter in specific modules, such as administration is. To do this, add this option to your app.yml:
all:
...
ga:
disabled_modules: [administration]
code: >
...
Allows you to define a script block in a template, and let it be included by the include_javascripts() helper after all js files, so that you can put it right before the body tag , to conform to the Yahoo performance best practice (see http://developer.yahoo.com/performance/rules.html#js_bottom).
First put this class in your /lib/response directory:
<?php /* * LfmWebResponse * @package forcemolle * @subpackage response * @author Jules Bernable <julius@laforcemolle.org> * */ class LfmWebResponse extends sfWebResponse { protected $inline_scripts = array(); public function initialize(sfEventDispatcher $dispatcher, $options = array()) { parent::initialize($dispatcher, $options); $this->inline_scripts = array_combine($this->positions, array_fill(0, count($this->positions), array())); } /** * Adds a script block to the current web response. * * @param string $script The JavaScript code * @param string $id A unique identifier for the script * @param string $position Position */ public function addInlineScript($script='', $position=sfWebResponse::LAST) { $this->validatePosition($position); $this->inline_scripts[$position][] = $script; } /** * Retrieves inline scripts from the current web response. * * By default, the position is sfWebResponse::ALL, * and the method returns all inline scripts ordered by position. * * @param string $position The position * * @return array An array of inline scripts */ public function getInlineScripts($position = self::ALL) { if (self::ALL === $position) { $inline_scripts = array(); foreach ($this->getPositions() as $position) { foreach ($this->inline_scripts[$position] as $script) { $inline_scripts[] = $script; } } return $inline_scripts; } else if (self::RAW === $position) { return $this->inline_scripts; } $this->validatePosition($position); return $this->inline_scripts[$position]; } }
Update your $sf_app_dir/config/factories.yml accordingly:
all:
response:
class: LfmWebResponse
Then you need to overwrite the symfony AssetHelper. First copy $sf_symfony_lib_dir/helper/AssetHelper.php to $sf_app_dir/lib/helper/AssetHelper.php (or to $sf_lib_dir/helper/AssetHelper.php if you want it enabled globally). Then replace the get_javascripts() function (line 492) by the following:
function get_javascripts() { $response = sfContext::getInstance()->getResponse(); sfConfig::set('symfony.asset.javascripts_included', true); $html = ''; foreach ($response->getJavascripts() as $file => $options) { $html .= javascript_include_tag($file, $options); } use_helper('JavascriptBase'); foreach ($response->getInlineScripts() as $script) { $html .= content_tag('script', javascript_cdata_section($script), array('type' => 'text/javascript')); } return $html; }
Et voilà ! You can now add a script in a template this way:
<?php $sf_response->addInlineScript(<<<EOF // Mootools FTW! window.addEvent('domready', function() { var foo = alert('BAR!'); }); EOF ); ?>
Your script block is now added after all js files added by include_javascripts() !
Please note that this is a basic implementation. You can not 'override' a script nor remove it afterwards.
function thumbnail($path, $maxWidth, $maxHeight, $params=null) { $path = ($path{0} == '/' ? '' : '/') . $path; $maxWidth = (int) $maxWidth; $maxHeight = (int) $maxHeight; $fileName = @end(explode('/', $path)); $thumbsDir = sfConfig::get('sf_web_dir').'/images/thumbs'; if(!file_exists($thumbsDir)) { mkdir($thumbsDir); chmod($thumbsDir, 0777); } $thumbName = $fileName.'-'.$maxWidth.'x'.$maxHeight.'.png'; $thumbPath = '/images/thumbs/'.$thumbName; $sourcePath = sfConfig::get('sf_web_dir').$path; $destPath = sfConfig::get('sf_web_dir').$thumbPath; if(!file_exists($destPath)) { $t = new sfThumbnail($maxWidth, $maxHeight); $t->loadFile($sourcePath); $t->save($destPath, 'image/png'); } $path = $thumbPath; return image_tag($path, $params); }
Usage similar to image_tag, directory web/images/thumbs with chmod 0777 need to be created. Depending on sfThumbnailPlugin
example:
echo thumbnail('bigPhoto.png', 100, 100, 'alt=myBigPhoto');
<?php class sfValidatorCpfCnpj extends sfValidatorBase { /** * Configures the current validator * * Available options (required): * * * tipo: CPF or CNPJ * * @param array $options An array of options * @param array $messages An array of error messages * * @see sfValidatorBase **/ protected function configure($options = array(), $messages = array()) { $this->addRequiredOption('tipo'); $this->setMessage('invalid', '%tipo% %value% não é válido.'); } protected function validaCPF($cpf) { $cpf = str_replace(array('.','-'),'',$cpf); $soma = 0; if (strlen($cpf) <> 11) return false; // Verifica 1º digito for ($i = 0; $i < 9; $i++): $soma += (($i+1) * $cpf[$i]); endfor; $d1 = ($soma % 11); if ($d1 == 10): $d1 = 0; endif; $soma = 0; // Verifica 2º digito for ($i = 9, $j = 0; $i > 0; $i--, $j++): $soma += ($i * $cpf[$j]); endfor; $d2 = ($soma % 11); if ($d2 == 10): $d2 = 0; endif; if ($d1 == $cpf[9] && $d2 == $cpf[10]): return true; else: return false; endif; } protected function validaCNPJ($cnpj) { $cnpj = str_replace(array('.','/','-'),'',$cnpj); if (strlen($cnpj) <> 14) return false; $soma = 0; $soma += ($cnpj[0] * 5); $soma += ($cnpj[1] * 4); $soma += ($cnpj[2] * 3); $soma += ($cnpj[3] * 2); $soma += ($cnpj[4] * 9); $soma += ($cnpj[5] * 8); $soma += ($cnpj[6] * 7); $soma += ($cnpj[7] * 6); $soma += ($cnpj[8] * 5); $soma += ($cnpj[9] * 4); $soma += ($cnpj[10] * 3); $soma += ($cnpj[11] * 2); $d1 = $soma % 11; $d1 = $d1 < 2 ? 0 : 11 - $d1; $soma = 0; $soma += ($cnpj[0] * 6); $soma += ($cnpj[1] * 5); $soma += ($cnpj[2] * 4); $soma += ($cnpj[3] * 3); $soma += ($cnpj[4] * 2); $soma += ($cnpj[5] * 9); $soma += ($cnpj[6] * 8); $soma += ($cnpj[7] * 7); $soma += ($cnpj[8] * 6); $soma += ($cnpj[9] * 5); $soma += ($cnpj[10] * 4); $soma += ($cnpj[11] * 3); $soma += ($cnpj[12] * 2); $d2 = $soma % 11; $d2 = $d2 < 2 ? 0 : 11 - $d2; if ($cnpj[12] == $d1 && $cnpj[13] == $d2): return true; else: return false; endif; } protected function doClean($value) { $tipo = $this->getOption('tipo'); if($tipo == 'CPF'): if(!$this->validaCPF($value)): throw new sfValidatorError($this, 'invalid', array('tipo' => $this->getOption('tipo'), 'value' => $value)); return false; endif; elseif($tipo == 'CNPJ'): if(!$this->validaCNPJ($value)): throw new sfValidatorError($this, 'invalid', array('tipo' => $this->getOption('tipo'), 'value' => $value)); return false; endif; else: throw new sfValidatorError($this, 'invalid', array('tipo' => $this->getOption('tipo'), 'value' => $value)); return false; endif; return $value; } } ?>
On occasions we want to move the front controllers of our application to another location and remove them from the web directory, without losing the relative directions to the directories css, js, images, etc..
For this we need to do two things.
For development environment: Replace the following line in the class sfWebRequest
568 $this->relativeUrlRoot = preg_replace('#/[^/]+\.php5?$#', '', $this->getScriptName());
by the following code
568 $relative = preg_replace('#/[^/]+\.php5?$#', '', $this->getScriptName()); 569 $webDir = str_replace('\\', '/', sfConfig::get('sf_web_dir')); 570 $intercept = '/'.implode('/', array_intersect(explode('/',$relative), explode('/',$webDir))); 571 $position = strpos($webDir, $intercept); 572 $this->relativeUrlRoot = substr($webDir, $position);
For production environment:: Create a filter relativeUrl.
class relativeUrlFilter extends sfFilter { public function execute($filterChain){ $context = $this->getContext(); $request = $context->getRequest(); $relative = preg_replace('#/[^/]+\.php5?$#', '', $request->getScriptName()); $webDir = str_replace('\\', '/', sfConfig::get('sf_web_dir')); $intercept = '/'.implode('/', array_intersect(explode('/',$relative), explode('/',$webDir))); $position = strpos($webDir, $intercept); $request->setRelativeUrlRoot(substr($webDir, $position)); $filterChain->execute(); } }
Active a filter in apps/yourapp/config/filters.yml
rendering: ~
security: ~
relativeUrl:
class: relativeUrlFilter
cache: ~
common: ~
execution: ~
and that's it. Now you can put the front controllers outside the web directory, for example, in the root directory of the application myproject/index.php and myproject/myapp_dev.php, or in another location within the web directory, for example, myproject/web/frontend/index.php and myproject/web/backend/index.php.
Note: Remember that if you move these files of the web directory, you must update the following line of code according to the new location.
require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php');
<div class="span-24 last"> <div id="map_canvas" class="span-22 prepend-1 append-1 last" style="height: 300px"></div> <div id="marker_info" style="height:300px; vertical-align:top; background-color:pink;" class="ui-helper-hidden">Some marker info</div> </div> <% if (false) { %> <script src="../../Scripts/jquery-1.3.2.min-vsdoc.js" type="text/javascript"></script> <script src="../../Scripts/jquery.validate-vsdoc.js" type="text/javascript"></script> <% } %> <script src="http://maps.google.com/maps?file=api&v=2&sensor=false&key=yerKey" type="text/javascript"></script> <script src="Scripts/jquery.bbq.js" type="text/javascript"></script> <script type="text/javascript"> var map; var marker; $(document).ready(function() { var ps = $.deparam.fragment(); var big = true; var point = new GLatLng(37.4419, -122.1419); if(ps.lat) { point = new GLatLng(ps.lat, ps.lng); } map = new GMap2(document.getElementById("map_canvas")); map.addControl(new GLargeMapControl()); map.addControl(new GNavLabelControl()); map.setCenter(point, 13); marker = new GMarker(point, {draggable: true}); GEvent.addListener(marker, "dragstart", function() { map.closeInfoWindow(); }); GEvent.addListener(marker, "dragend", function() { point = marker.getLatLng(); location.hash = "#lat=" + point.lat() + "&lng=" + point.lng(); }); GEvent.addListener(marker, "click", function() { $('#map_canvas').removeClass('last').removeClass('span-22').addClass('span-18'); $('#marker_info').addClass('span-4').addClass('last').show(); if(big) { map.panBy(new GSize(-90, 0)); big = false; } }); map.addOverlay(marker); }); </script>
Sometimes, when you display the form for a Doctrine object, you want to make some fields read-only..
You can unset the field, and just display the value in the template. But it's better to still display an input form. You can also mess with the template, etc.
Here's a validator to achive the same goal, with no need to touch the template.
class sfValidatorReadOnlyField extends sfValidatorBase { /** * Configures the current validator * * Available options (required): * * * object: The current form object * * field: The current form field * * Available error codes: * * * changed * * @param array $options An array of options * @param array $messages An array of error messages * * @see sfValidatorBase **/ protected function configure($options = array(), $messages = array()) { $this->addRequiredOption('object'); $this->addRequiredOption('field'); $this->addMessage('changed', 'You don\'t want to mess with me, kid!'); } /** * @see sfValidatorBase */ protected function doClean($value) { // Ok, we could just reset the value, but it's so more fun this way $object = $this->getOption('object'); if ($value != $object->get($this->getOption('field'))) { throw new sfValidatorError($this, 'changed'); } return (string) $value; } }
In your form class, use this code :
public function configure() { … // make some fields readonly $this->widgetSchema['first_name']->setAttribute('readonly', 'readonly'); $this->widgetSchema['last_name']->setAttribute('readonly', 'readonly'); $this->widgetSchema['email_address']->setAttribute('readonly', 'readonly'); $this->validatorSchema['first_name'] = new sfValidatorReadOnlyField(array('field' => 'first_name', 'object' => $this->getObject())); $this->validatorSchema['last_name'] = new sfValidatorReadOnlyField(array('field' => 'last_name', 'object' => $this->getObject())); $this->validatorSchema['email_address'] = new sfValidatorReadOnlyField(array('field' => 'email_address', 'object' => $this->getObject())); … }
<textarea id="comment" style="height:35px;max-height:100px; line-height:12px; font-size:12px;" class="span-24 last"></textarea>
<div id="b" class="span-24 last"> <button id="vid">Vid</button> <button id="pic">Pic</button> </div> [javascript] <script type="text/javascript"> $(document).ready(function() { var vidIndex = 0; var vidList = new Array(); $('#vid').click(function() { $('#vidContainer').slideDown(); }); $('#closeVid').click(function() { $('#vidContainer').slideUp(); }); $('#addVid').click(function() { $('#vidList').append('<div id="vid'+vidIndex+'"><span class="video">' + $('#vidUrl').val() + '</span> <button title="'+vidIndex+'" class="removeVid">Remove</button></div>'); ++vidIndex; var chosen = ''; $('.video').each(function () { chosen += $(this).text() + ','; }); $('#params').html(chosen); }); $('.removeVid').live("click", function(){ var index = $(this).attr('title'); $('#vid'+index).remove(); --vidIndex; var chosen = ''; $('.video').each(function () { chosen += $(this).text() + ','; }); $('#params').html(chosen); }); </script>
In case you have a populated database and you need to generate a fixtures for it, you can use symfony propel:data-dump tasks or manually call methods of sfPropelData.
But If your database contains too much data to be fetched, this methods will not work for you.
For such cases I have extended sfPropelData to use Propel Criteria objects to filter dumped data. For example, you can set limit 30 records for every model to prevent dumping all records from database.
You can create special tasks to generate fixtures and use there class listed below
$data = new fxPropelData(); // Defining criterias $c1 = new Criteria(); $c1->setLimit(30); $c2 = clone($c1); $c2->add('IS_DELETED',0); // array with models and criterias for them $models = array( 'Fruit'=> $c1, 'Vegetable' => $c2, ); $data->dumpDataWithCriteria(sfConfig::get('sf_data_dir').'/fixtures/food.yml',$models, $options['connection']);
class fxPropelData extends sfPropelData { public function dumpDataWithCriteria($directoryOrFile, $models = array(), $connectionName = null) { $dumpData = $this->getDataWithCriteria($models, $connectionName); // save to file(s) if (!is_dir($directoryOrFile)) { file_put_contents($directoryOrFile, sfYaml::dump($dumpData, 3)); } else { $i = 0; foreach ($models as $tableName => $criteria) { if (!isset($dumpData[$tableName])) { continue; } file_put_contents(sprintf("%s/%03d-%s.yml", $directoryOrFile, ++$i, $tableName), sfYaml::dump(array($tableName => $dumpData[$tableName]), 3)); } } } protected function getDataWithCriteria($models, $connectionName = 'propel') { $this->loadMapBuilders(); $this->con = Propel::getConnection($connectionName); $this->dbMap = Propel::getDatabaseMap($connectionName); $dumpData = array(); foreach ($models as $model => $criteria) { $tableName = $model; $tableMap = $this->dbMap->getTable(constant(constant($tableName.'::PEER').'::TABLE_NAME')); $hasParent = false; $haveParents = false; $fixColumn = null; foreach ($tableMap->getColumns() as $column) { $col = strtolower($column->getName()); if ($column->isForeignKey()) { $relatedTable = $this->dbMap->getTable($column->getRelatedTableName()); if ($tableName === $relatedTable->getPhpName()) { if ($hasParent) { $haveParents = true; } else { $fixColumn = $column; $hasParent = true; } } } } if ($haveParents) { // unable to dump tables having multi-recursive references continue; } // get db info $resultsSets = array(); if ($hasParent) { $resultsSets[] = $this->fixOrderingOfForeignKeyDataInSameTableWithCriteria($resultsSets, $criteria, $tableName, $fixColumn); } else { $stmt = call_user_func(array($tableName.'Peer', 'doSelectStmt'),$criteria,$this->con); $resultsSets[] = array_merge($stmt->fetchAll(PDO::FETCH_ASSOC),$resultsSets); $stmt->closeCursor(); unset($stmt); } foreach ($resultsSets as $rows) { if(count($rows) > 0) { if (!isset($dumpData[$tableName])) $dumpData[$tableName] = array(); foreach ($rows as $row) { $pk = $tableName; $values = array(); $primaryKeys = array(); $foreignKeys = array(); foreach ($tableMap->getColumns() as $column) { $col = $column->getName(); $isPrimaryKey = $column->isPrimaryKey(); if (is_null($row[$col])) { continue; } if ($isPrimaryKey) { $value = $row[$col]; $pk .= '_'.$value; $primaryKeys[$col] = $value; } if ($column->isForeignKey()) { $relatedTable = $this->dbMap->getTable($column->getRelatedTableName()); if ($isPrimaryKey) { $foreignKeys[$col] = $row[$col]; $primaryKeys[$col] = $relatedTable->getPhpName().'_'.$row[$col]; } else { $values[$col] = $relatedTable->getPhpName().'_'.$row[$col]; $values[$col] = strlen($row[$col]) ? $relatedTable->getPhpName().'_'.$row[$col] : ''; } } elseif (!$isPrimaryKey || ($isPrimaryKey && !$tableMap->isUseIdGenerator())) { // We did not want auto incremented primary keys $values[$col] = $row[$col]; } } if (count($primaryKeys) > 1 || (count($primaryKeys) > 0 && count($foreignKeys) > 0)) { $values = array_merge($primaryKeys, $values); } $dumpData[$tableName][$pk] = $values; } } } } return $dumpData; } protected function fixOrderingOfForeignKeyDataInSameTableWithCriteria($resultsSets, $criteria, $tableName, $column, $in = null) { is_null($in) ? $criteria->add($column->getName(),NULL) : $criteria->add($column->getName(),$in,Criteria::IN); $stmt = call_user_func(array($tableName.'Peer', 'doSelectStmt'),$criteria,$this->con); $in = array(); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $in[] = "'".$row[strtolower($column->getRelatedColumnName())]."'"; $resultsSets[] = $row; } if ($in = implode(', ', $in)) { $resultsSets = $this->fixOrderingOfForeignKeyDataInSameTableWithCriteria($resultsSets, $tableName, $column, $in); } return $resultsSets; } }
The symfony routing system is tied to the sfContext resource via the factory handler.
It might an issue if you need to build routes in the cli mode, where sfContext has no instance. Sample: one of your cron jobs sends emails that contain links.
The best way I've found is to provide an access to the routing system linked to the ProjectConfiguration class. Add a static protected "$routing" variable and a static public method "getRouting()" :
class ProjectConfiguration extends sfProjectConfiguration { static protected $routing = null # Symfony routing system ; /** * Returns the routing resource for the active application */ static public function getRouting() { if (null !== self::$routing) { return self::$routing; } // If sfContext has an instance, returns the loaded routing resource if (sfContext::hasInstance() && sfContext::getInstance()->getRouting()) { self::$routing = sfContext::getInstance()->getRouting(); } else { // Initialization if (!self::hasActive()) { throw new sfException('No sfApplicationConfiguration loaded'); } $appConfig = self::getActive(); $config = sfFactoryConfigHandler::getConfiguration($appConfig->getConfigPaths('config/factories.yml')); $params = array_merge( $config['routing']['param'], array( 'load_configuration' => false, 'logging' => false, 'context' => array( 'host' => sfConfig::get('app_host', 'localhost'), 'prefix' => sfConfig::get('app_prefix', sfConfig::get('sf_no_script_name') ? '' : '/'.$appConfig->getApplication().'_'.$appConfig->getEnvironment().'.php'), 'is_secure' => sfConfig::get('app_host', false), ), ) ); $handler = new sfRoutingConfigHandler(); $routes = $handler->evaluate($appConfig->getConfigPaths('config/routing.yml')); $routeClass = $config['routing']['class']; self::$routing = new $routeClass($appConfig->getEventDispatcher(), null, $params); self::$routing->setRoutes($routes); } return self::$routing; } }
In the method, the 'context' parameter needs further explanations.
In a classic context, symfony is loaded after a web request. Sample: https://www.example.com/frontend_dev.php/products
This request provides data that are reused when sf needs to generate a new dynamic url:
In the cli mode, as Symfony is not loaded by a request, you have to provide these parameters (prefix can be guessed).
In app.yml, add:
all:
host: www.example.com
is_secure: false
Now, let's see how to generate the link. If your routing.yml file looks like it:
product_show: url: /products/:category_slug/:slug class: sfDoctrineRoute options: { model: Product, type: object } # ...
Then building the link is as simple as:
class Product extends BaseProduct { /** * Returns the public url */ public function getUrl() { return ProjectConfiguration::getRouting()->generate('product', $this, true); } /* ... */ }
Regards.