<?xml version="1.0" encoding="ISO-8859-1"?>
<rss version="2.0">
	<channel>
		<title>Bug Features - Free bug report and feature request service for your Web site.</title>
		<link>http://bugfeatures.com/blog/index.php</link>
		<description><![CDATA[BugFeatures is FREE!]]></description>
		<copyright>Copyright 2010, Dan</copyright>
		<managingEditor>Dan</managingEditor>
		<language>en-US</language>
		<generator>SPHPBLOG 0.4.8</generator>
		<item>
			<title>Zend Mail mangles email text</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry100117-161034</link>
			<description><![CDATA[Had this strange problem ever since we upgraded a Symfony site from 1.0 to 1.2 - the emails being logged to the database were being mangled.<br /><br />The problem turns out to be simply that the symfony 1.2 swToolkit plugin uses Zend Mail instead of phpMailer and Zend Mail spits out encoded text when you ask it for the email text or HTML contents.<br /><br />So the simple fix is to wrap the data you get out of Zend Mail with the obscure quoted_printable_decode() function as shown here:<br /><br /><pre>$message = quoted_printable_decode($zendEmail-&gt;getBodyHtml()-&gt;getContent());<br />$text = quoted_printable_decode($zendEmail-&gt;getBodyText()-&gt;getContent());<br /></pre><br />There, that gets rid of all those =0D=0A characters everywhere!<br /><br />Note that you would think you could avoid this simply by passing the line ending explicitly, but it does not work:<br /><pre>//this does *NOT* work!<br />$message = $zendEmail-&gt;getBodyHtml()-&gt;getContent(&quot;\r\n&quot;);<br />$text = $zendEmail-&gt;getBodyText()-&gt;getContent(&quot;\r\n&quot;);<br /></pre>]]></description>
			<category>BugFeatures, Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry100117-161034</guid>
			<author>Dan</author>
			<pubDate>Mon, 18 Jan 2010 00:10:34 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=10&amp;m=01&amp;entry=entry100117-161034</comments>
		</item>
		<item>
			<title>Sendmail woes</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry100113-194612</link>
			<description><![CDATA[I have battled postfix, qmail and sendmail for years - and my latest trouble involved sendmail trying to route emails to local (non-existant) users, even though the domains in question were not in the local domains config.<br /><br />After running around in config circles for a couple of hours it turns out that if you set the server&#039;s hosts to include your domains, sendmail will assume local delivery for those domains.<br /><br />That is even if you have sendmail configured not to (ie: in /etc/mail/local-host-names). It even ignores your virtusertable with explicit instructions to forward mail for those domains elsewhere.<br /><br />Go figure. Hope this helps someone.]]></description>
			<category>Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry100113-194612</guid>
			<author>Dan</author>
			<pubDate>Thu, 14 Jan 2010 03:46:12 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=10&amp;m=01&amp;entry=entry100113-194612</comments>
		</item>
		<item>
			<title>Restore single table from HUGE mysqldump file</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry091117-232320</link>
			<description><![CDATA[That&#039;s one of the problems with &#039;enterprise&#039; websites - huge databases. Knowing that everything is backed up, it is occasionally tempting to run some ad-hoc handmade &#039;looks about right&#039; SQL statements every now and then - and occasionally they don&#039;t work out too well.<br /><br />As it happens, restoring a table from a 5GB SQL backup can actually be a pretty time consuming task, not to mention tedious.<br /><br />Faced with this slightly daunting task I did what I usually do when faced with daunting tasks - checked how other people did it :)<br /><br />So a big thanks to ThatsLinux for sharing a nice little shell script that takes the pain out of extracting one little table from a mammoth sql script. Kudos.<br /><br /><a href="http://thatslinux.blog.co.uk/2009/10/10/extracting-individual-tables-from-mysqldump-full-backups-7140415/" target="_blank" >ThatsLinux</a>]]></description>
			<category>Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry091117-232320</guid>
			<author>Dan</author>
			<pubDate>Wed, 18 Nov 2009 07:23:20 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=11&amp;entry=entry091117-232320</comments>
		</item>
		<item>
			<title>Chrome Explorer</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry090924-174119</link>
			<description><![CDATA[All right now Google are installing Chrome inside Internet Explorer so now Microsoft can claim to have a standards compliant browser. But what I&#039;m excited about is viewing it all inside Firefox with the ieTab plugin!<br /><br />That&#039;s Firefox rendering Internet Explorer rendering Chrome.<br /><br />Now somebody create a chrome plugin that uses the Opera rendering engine or - even better - the Firefox rendering engine!<br /><br />Whoop!]]></description>
			<category>BugFeatures, Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry090924-174119</guid>
			<author>Dan</author>
			<pubDate>Fri, 25 Sep 2009 00:41:19 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=09&amp;entry=entry090924-174119</comments>
		</item>
		<item>
			<title>Integrated framework commands</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry090915-163212</link>
			<description><![CDATA[When I get the urge to run a symfony command from an explorer window I have a few options...<br /><br /><b>Command prompt here</b><br />Well this is handy but you still have to type out your command. Fortunately I have PowerCmd which lets you save common commands and make them toolbar buttons.<br /><br /><b>Directory Opus</b><br />My favourite explorer replacement lets me type commands directly into the toolbar - and it *remembers* the history of commands. Nice.<br /><br /><img src="images/do.jpg" width="400" height="34" border="0" alt="" /><br /><br /><b>XYplorer</b><br />Another good file manager - and a bit more lightweight than Directory Opus - has a great scripting engine. Check this out:<br /><br /><pre>::if (confirm(&quot;propel-build-ALL?!&quot;)){<br />run &#039;php symfony propel-build-all&#039;,&lt;curpath&gt;<br />}</pre><br />That&#039;s not bad. And you can make it a favourite (category) that will execute on the current activated folder. Again, nice.<br /><br /><img src="images/xy.jpg" width="308" height="68" border="0" alt="" /><br /><br /><b>PhpEd</b><br />Well this isn&#039;t exactly an explorer replacement, but seeing as though I never close my IDE it is technically a file manager ;) And as I posted <a href="http://bugfeatures.com/blog/index.php?entry=entry090419-175132" target="_blank" >here</a>, you can add scripts to the right-click menu. The biggest drawback is that you can&#039;t put &lt; characters in your scripts, which means you can&#039;t load sql files into mysql without requiring a seperate DOS batch file.<br /><br /><img src="images/phpedcli.jpg" width="238" height="594" border="0" alt="" />]]></description>
			<category>Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry090915-163212</guid>
			<author>Dan</author>
			<pubDate>Tue, 15 Sep 2009 23:32:12 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=09&amp;entry=entry090915-163212</comments>
		</item>
		<item>
			<title>Modify view for an environment</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry090827-042652</link>
			<description><![CDATA[This is something that has been discussed in the Symfony google groups but was unresolved - how to modify a view configuration based on the environment. Some developers say you should not be doing this, as the view should be the same in all environments, but let me explain why I needed do do it...<br /><br />I have some javascripts loaded from google to speed up my sites - anyone who has used the Yahoo YSlow plugin for FireBug will be familiar with the &quot;F&quot; rating for content delivery networks (CDN) score. But I have a developer on my team who works offline and wants to be able to develop locally without having to constantly modify all the view.yml files (and there are a few!) for each app.<br /><br />So, although it would be nice to be able to specify an environment in the view.yml the same way you can in the settings.yml, some folks forget that you can put PHP code in your view.yml to be evaluated at runtime (or at least read from the cache at runtime!).<br /><br />So, this is how we manage to serve google&#039;s minified high speed Javascript libraries in the &quot;live&quot; environment but use local copies elsewhere:<br /><pre>default:<br /> javascripts:<br />    - &lt;?php echo (strpos(SF_ENVIRONMENT,&quot;live&quot;)!==false?&#039;http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js&#039;:&#039;/js/prototype&#039;).PHP_EOL ?&gt;<br />    - &lt;?php echo (strpos(SF_ENVIRONMENT,&quot;live&quot;)!==false?&#039;http://ajax.googleapis.com/ajax/libs/scriptaculous/1.8.2/scriptaculous.js?load=effects&#039;:&#039;/js/scriptaculous/scriptaculous.js?load=effects&#039;).PHP_EOL ?&gt;<br /></pre><br />Damn, that was easy, wasn&#039;t it?<br />:-)]]></description>
			<category>BugFeatures, Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry090827-042652</guid>
			<author>Dan</author>
			<pubDate>Thu, 27 Aug 2009 11:26:52 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=08&amp;entry=entry090827-042652</comments>
		</item>
		<item>
			<title>Scalability and frameworks - Part 2</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry090809-055809</link>
			<description><![CDATA[Well, in case you were wondering, I managed to get around my scalability problem by focussing on PHP&#039;s strengths and side-stepping it&#039;s weaknesses. So in this particular problem I was facing 2 primary bottlenecks - yaml parsing and propel memory leaks. <br /><br />I got around the yaml issue by using some custom string hacking - and PHP&#039;s string functions are pretty quick. By using split() and strpos() I could slice up my big yaml file into more digestible sizes (see previous post).<br /><br />The propel issue, however, was always going to be more problematic. I considered removing all my ORM calls and either using delayed inserts or LOAD DATA INFILE statements. Both of which I have used successfully in the past, but implementing it in this case meant a complete rewrite of over 2000 lines of tested code.<br /><br />So, I decided to use a similar approach to the ORM. Like YAML, Propel 1.2 is very good at what it does inside small to medium processes. It just isn&#039;t very efficient when used in big jobs - like inside large loops. So instead of trying to process my massive array that I managed to construct from the original yaml, I sliced it up and sent smaller yaml files back into the queue.<br /><br />What this means is that instead of having a single item of 13,000 recipients sitting in the queue I have 130 items of 100 instead. My queue processing task can now batch 100 emails at a time and then terminate, thereby freeing up memory for the next batch. While there is some extra overhead in writing files to disk and duplicating some of the yaml, the benefits are well worth it. The end result is a scalable, fast and robust system that can handle almost any request by simply slicing it up into manageable chunks. TIme and money saved, client happy :)]]></description>
			<category>BugFeatures, Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry090809-055809</guid>
			<author>Dan</author>
			<pubDate>Sun, 09 Aug 2009 12:58:09 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=08&amp;entry=entry090809-055809</comments>
		</item>
		<item>
			<title>Scalability and frameworks</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry090807-142608</link>
			<description><![CDATA[This is a problem I often come up against - sending email to thousands of recipients. The way I usually handle it is to do as little as possible in the master loop.<br /><br />The best technique I have found is to defer database writes by writing to a temporary textfile in memory (using php://memory) and then using LOAD DATA INFILE to write contents to the database. That puts all the email data into a database queue, allowing a cron task to send the emails - say 50 per minute.<br /><br />Using this technique I can send 50,000+ emails quite easily. The queue function takes under a minute to run and the cron job hacks away at the queue for about 24 hours or so.<br /><br />The problem I have with this technique is that it evolved via a process of whittling away at the various framework layers used in my &quot;enterprise applications&quot;. Framework layers supposedly designed to provide scalability. <br /><br />Now you can probably tell from this blog that I use Symfony extensively (although I have dabbled with other frameworks, I always come back to Symfony). Perhaps it is due to the fact that my largest projects (over 1,000,000 lines of code) are locked into Symfony 1.0 and - more importantly - Propel 1.2. Arguably, what I should be doing is writing a Symfony plugin that performs large data processing using the techniques I&#039;ve described, but it seems a shame to have a detailed, documented and unit-tested ORM that can only be used for serving small jobs like &quot;web pages&quot;, and must be bypassed for any data intensive stuff. Isn&#039;t that a core requirement of &quot;enterprise level&quot; scalability?<br /><br />Anyway, I&#039;ll try to end the rant with some code. Last night I wrote a custom yaml parser because the sfYaml::load() method in Symfony 1.0 (which uses spyc) was taking over 20 minutes to load a 1MB yaml file. I managed to load it in about 5 seconds. Now, I&#039;ll admit it&#039;s not really a yaml parser but more of a key-pair extractor but because it doesn&#039;t use any regex it is waay faster than the old sfYaml. <br /><br />(NB: I couldn&#039;t install syck on my CentOS4 server and the newer sfYaml component from 1.2 failed to load yaml collections in the format shown below).<br /><br />The yaml (multiply this by 10,000):<br /><pre>Recipients:<br />  - title: &#039;Mr&#039;<br />    firstname: &#039;Bob&#039;<br />    lastname: &#039;Dobbs&#039;<br />    company: &#039;Subgenius Network&#039;<br />    email: &#039;bob@subgenius.com&#039;<br />  - title: &#039;Mrs&#039;<br />    firstname: &#039;Jane&#039;<br />    lastname: &#039;Dobbs&#039;<br />    company: &#039;Subgenius Network&#039;<br />    email: &#039;jane@subgenius.com&#039;</pre><br />The recipient extractor method:<br /><pre>  public static function extractRecipients($yaml)<br />  {<br />    //separate recipients from yaml<br />    $recipients_string = &quot;Recipients:&quot;;<br />    $recipients_start = strpos($yaml,$recipients_string);<br />    $recipients_end = strpos($yaml,&quot;bcc:&quot;);<br />    $recipients_yaml = substr($yaml,$recipients_start,<br />                      ($recipients_end-$recipients_start));<br />    $yaml_start =  substr($yaml,0,$recipients_start);<br />    $yaml_end =  substr($yaml,$recipients_end);<br />    $yaml = $yaml_start . $yaml_end;<br />    <br />    //load yaml<br />    $yaml_array = sfYaml::load($yaml);<br />    <br />    //get recipients array<br />    $recipients_yaml = str_replace($recipients_string,&quot;&quot;,<br />                       $recipients_yaml);<br />    $recipients_array = split(&quot;-&quot;,$recipients_yaml);<br />    //loop over recipients to get getails as array<br />    $recipients_list = array();<br />    foreach ($recipients_array as $r1) {<br />      //loop over each line item<br />      $r1_array = split(&quot;\r\n&quot;,$r1);<br />      $my_recipient = array();<br />      foreach ($r1_array as $r2) {<br />        if (strpos($r2,&quot;:&quot;)!==false){<br />          //extract key pair from line item<br />          $r2_array = split(&quot;:&quot;,$r2);<br />          if (sizeof($r2_array) == 2){<br />            $key = str_replace(&quot;&#039;&#039;&quot;,&quot;&#039;&quot;,<br />                   trim(trim($r2_array[0]),&quot;&#039;&quot;));<br />            $value = str_replace(&quot;&#039;&#039;&quot;,&quot;&#039;&quot;,<br />                     trim(trim($r2_array[1]),&quot;&#039;&quot;));<br />            $my_recipient[$key] = $value;<br />          }<br />        }<br />      }<br />      if (sizeof($my_recipient) &gt; 1){<br />        //add this recipient to list<br />        $recipients_list[] = $my_recipient;<br />      }<br />    }<br />    //add recipients to yaml data<br />    $yaml_array[trim($recipients_string,&quot;:&quot;)] = $recipients_list;<br />    <br />    //return yaml data<br />    return $yaml_array; <br />  }</pre><br />]]></description>
			<category>BugFeatures, Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry090807-142608</guid>
			<author>Dan</author>
			<pubDate>Fri, 07 Aug 2009 21:26:08 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=08&amp;entry=entry090807-142608</comments>
		</item>
		<item>
			<title>BugFeatures blog open for comments!</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry090730-163518</link>
			<description><![CDATA[I have enabled comments on the blog after a few requests for it... play nice!]]></description>
			<category>BugFeatures, Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry090730-163518</guid>
			<author>Dan</author>
			<pubDate>Thu, 30 Jul 2009 23:35:18 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=07&amp;entry=entry090730-163518</comments>
		</item>
		<item>
			<title>Symfony and PhpBB3 integration</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry090730-155649</link>
			<description><![CDATA[I recently had to integrate a forum into a Symfony 1.0 app and, having selected PhpBB3 for the job, went about writing an authentication module. Here&#039;s how...<br /><br />1. Create the auth module<br /><br />Lets say I want to call the module &#039;symfony&#039;. I create a file called auth_symfony.php and drop it in the phpbb /includes/auth folder.<br /><br />2. Write the autologin method<br /><br />Because I want my forums to be on the same domain, I have saved the phpbb files in web/forum. This way I have access to the session variables stored in my symfony app. So when a user goes into the forums, the autologin method can be used to interrogate my smyfony session.<br /><br />In a nutshell, the autologin method needs to verify the user&#039;s session, grab their details and log them into phpbb. So as to avoid having to manage forum users from my symfony app, I also create new phpbb users here if one doesn&#039;t already exist for the current user.<br /><br />Note that phpbb needs its own session, so we need to switch between sessions here as well.<br /><pre>/**<br />* Autologin function<br />*/<br />function autologin_symfony()<br />{<br />  include_once(&#039;includes/functions_user.php&#039;);<br />  global $db, $config, $user;<br /><br />  $sess = session_name();<br />  session_name(&#039;symfony&#039;);<br />  session_start();<br />  <br />  $sfSession = $_SESSION[&#039;symfony/user/sfUser/attributes&#039;][&#039;userData&#039;];<br /><br />  @session_name($sess);<br />  @session_start();<br /><br />  <br />   if (isset($_REQUEST[&#039;admin&#039;])){<br />    $_SESSION[&#039;admin&#039;] = $_REQUEST[&#039;admin&#039;];<br />   }<br /><br /><br />  if (isset($_SESSION[&#039;data&#039;])){<br />   return $_SESSION[&#039;data&#039;];    <br />  }elseif (!isset($sfSession[&#039;username&#039;]) &amp;&amp; <br />    !isset($_SESSION[&#039;admin&#039;])){<br />    header(&quot;Location: /&quot;);<br />    exit;<br />  }elseif (isset($sfSession[&#039;username&#039;])){<br />    $user_row = array(<br />      &#039;username&#039;              =&gt; $sfSession[&#039;username&#039;],<br />      &#039;user_password&#039;         =&gt; phpbb_hash($sfSession[&#039;username&#039;]),<br />      &#039;user_email&#039;            =&gt; $sfSession[&#039;email&#039;],<br />      &#039;user_type&#039;             =&gt; USER_NORMAL,<br />      &#039;group_id&#039;              =&gt; 2<br />    );<br />    <br />    $sql =&#039;SELECT *<br />      FROM &#039; . USERS_TABLE . &quot;<br />      WHERE user_email = &#039;&quot; <br />       . $db-&gt;sql_escape(utf8_clean_string($sfSession[&#039;email&#039;]))<br />       .&quot;&#039;&quot;;<br />    $result = $db-&gt;sql_query($sql);<br />    $row = $db-&gt;sql_fetchrow($result);<br />    $db-&gt;sql_freeresult($result);<br /><br />    if ($row){  <br />      // Successful login...<br />      $data =  array_merge($row,array(<br />        &#039;status&#039;       =&gt; LOGIN_SUCCESS,<br />        &#039;error_msg&#039;    =&gt; false,<br />        &#039;autologin&#039;    =&gt; 1,<br />        &#039;user_row&#039;     =&gt; $row<br />      ));<br />      $_SESSION[&#039;data&#039;] = $data;<br />      return $data;<br />    }else{<br />      //check for existing name<br />      $sql =&#039;SELECT *<br />      FROM &#039; . USERS_TABLE . &quot;<br />      WHERE username_clean = &#039;&quot; <br />       . $db-&gt;sql_escape(utf8_clean_string($sfSession[&#039;username&#039;]))<br />       .&quot;&#039;&quot;;<br />      $result = $db-&gt;sql_query($sql);<br />      $row = $db-&gt;sql_fetchrow($result);<br />      $db-&gt;sql_freeresult($result);<br />       if ($row){<br />         //randomise username if duplicate found<br />         $user_row[&#039;username&#039;] = $user_row[&#039;username&#039;] <br />         . &quot; - &quot; . rand(1000,9999);<br />       }<br />      //create new user<br />      $user_id = user_add($user_row); <br />      $user_row[&#039;user_id&#039;] = $user_id;<br />      $data = array_merge($user_row,array(<br />        &#039;status&#039;      =&gt; LOGIN_SUCCESS_CREATE_PROFILE,<br />        &#039;error_msg&#039;   =&gt; false,<br />        &#039;autologin&#039;   =&gt; 1,<br />        &#039;user_row&#039;    =&gt; $user_row,<br />        &#039;user_type&#039;   =&gt; USER_NORMAL,<br />        &#039;group_id&#039;    =&gt; 2<br />      ));<br />      $_SESSION[&#039;data&#039;] = $data;<br />      return $data;<br />    }<br />  }<br />}</pre><br />Note also that the $_SESSION[&#039;data&#039;] array is used to avoid hitting the database everytime the user loads a new page in phpbb.<br /><br />3. Create admin login<br /><br />You might have noticed that the above code looks for a session var called &#039;admin&#039;. I use this to track whether a user has come from the backend. This is used to display the phpbb login form. Otherwise the user is simply sent back to the symfony app login.<br /><pre>/**<br />* Login function<br />*/<br />function login_symfony($username, $password)<br />{   <br />  global $db, $config, $user;<br /><br />     $sql =&#039;SELECT * FROM &#039; . USERS_TABLE <br />       . &quot; WHERE username_clean = &#039;&quot;<br />       .$db-&gt;sql_escape(utf8_clean_string($username))<br />       .&quot;&#039; and user_password = &#039;&quot;.md5($password).&quot;&#039;&quot;;<br />    $result = $db-&gt;sql_query($sql);<br />    $row = $db-&gt;sql_fetchrow($result);<br />    $db-&gt;sql_freeresult($result);<br /><br />    if ($row){<br />      <br />      $data = array_merge($row,array(<br />        &#039;status&#039;      =&gt; LOGIN_SUCCESS,<br />        &#039;error_msg&#039;   =&gt; false,<br />        &#039;user_row&#039;    =&gt; $row<br />      ));<br />      <br />      $_SESSION[&#039;data&#039;] = $data;<br />      return $data;<br />      <br />    }else{<br />  <br />      return array(<br />        &#039;status&#039;    =&gt; LOGIN_ERROR_PASSWORD,<br />        &#039;error_msg&#039; =&gt; &#039;LOGIN_ERROR_PASSWORD&#039;,<br />        &#039;user_row&#039;  =&gt; array(&#039;user_id&#039; =&gt; ANONYMOUS),<br />      );<br />    }<br />}</pre><br />4. Create logout method<br /><br />This simply wipes both sessions and redirects the user to the symfony app homepage.<br /><pre>/**<br />* Logout function<br />*/<br />function logout_symfony($user_row)<br />{<br />  $sess = session_name();<br />  session_name(&#039;symfony&#039;);<br />  session_start();<br />  session_destroy();<br />  <br />  @session_name($sess);<br />  @session_start();<br />  @session_destroy();<br />  header(&quot;Location: /&quot;);<br />  exit;<br />}</pre><br />5. Create validate session method<br /><br />Last but not least this method is used to check that the current user session is valid. Not 100% required, but a good precaution.<br /><pre>/**<br />* Validate session function<br />*/<br />function validate_session_symfony()<br />{<br />  $sess = session_name();<br />  session_name(&#039;symfony&#039;);<br />  session_start();<br />  $auth = $_SESSION[&#039;symfony/user/sfUser/authenticated&#039;];<br />  <br />  @session_start($sess);<br />  @session_start();<br />  //always redirect to home<br />  $admin = isset($_SESSION[&#039;admin&#039;]);<br />  if ($admin || $auth){<br />    return true; <br />  }<br />  <br />  return false;<br /><br />}</pre><br />6. Configure<br /><br />You&#039;ll need to log into the phpbb ACP and set the authentication module to your custom module. Then you need to bypass symfony in the .htaccess for the /forum URL<br /><pre>RewriteRule ^forum/.*$ - [PT]<br />RewriteCond %{REQUEST_URI} !^/forum </pre><br />And that&#039;s basically it :)<br />]]></description>
			<category>BugFeatures, Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry090730-155649</guid>
			<author>Dan</author>
			<pubDate>Thu, 30 Jul 2009 22:56:49 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=07&amp;entry=entry090730-155649</comments>
		</item>
		<item>
			<title>Something nice to say about EFI-X</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry090723-004857</link>
			<description><![CDATA[A lot of people moan about EFI-X so I thought I&#039;d tell a good story. I got my EFI-X chip about 6 months ago. After a botched OS X 10.5.7 update the chip fried. I sent it back. In the interim I toyed with every OSx86 kernel out there without any success. Then today my chip arrived back from Taiwan (desplite the &quot;made in Holland&quot; insignia). I popped it back into the PC and installed OSX again from a retail DVD first time no trouble at all. To be honest, I doubted I&#039;d ever see that chip again, but now I&#039;m feeling better...]]></description>
			<category>BugFeatures, Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry090723-004857</guid>
			<author>Dan</author>
			<pubDate>Thu, 23 Jul 2009 07:48:57 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=07&amp;entry=entry090723-004857</comments>
		</item>
		<item>
			<title>Send JWPlayer flashvars from shadowbox</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry090721-205251</link>
			<description><![CDATA[<a href="http://www.shadowbox-js.com/" target="_blank" >Shadowbox </a> is a great way to play FLV videos in a lightbox, but it is a little shortsighted in terms of the parameters it expects you&#039;ll want to pass onto <a href="http://www.longtailvideo.com/players/jw-flv-player/" target="_blank" >JWPlayer</a>. So here&#039;s a little workaround I came up with.<br /><br />If you want to send a playlist position and repeat variable you need to modify the shadowbox-flv.js file as follows:<br /><br /><pre>var D=[&quot;file=&quot;+this.obj.content,&quot;height=&quot;+F,&quot;width=&quot;+I,<br />&quot;autostart=&quot;+C,&quot;displayheight=&quot;+K,&quot;showicons=&quot;+H,<br />&quot;backcolor=0x000000&quot;,&quot;frontcolor=0xCCCCCC&quot;,&quot;lightcolor=0x557722&quot;,<br />&#039;repeat=&#039;+(this.obj.repeat?this.obj.repeat:&#039;false&#039;),<br />&#039;playlist=&#039;+(this.obj.playlist?this.obj.playlist:&#039;none&#039;)]</pre><br />Then you can send the params from your JS code:<br /><pre> Shadowbox.open({<br />        player:     &#039;flv&#039;,<br />        title:      &#039;FLV&#039;,<br />        content:    &#039;playlist.xml&#039;,<br />        height:      400,<br />        width:       550,<br />        autostart:   true,<br />        repeat:     &#039;list&#039;,<br />        playlist:   &#039;right&#039;<br /><br />    });</pre>]]></description>
			<category>BugFeatures, Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry090721-205251</guid>
			<author>Dan</author>
			<pubDate>Wed, 22 Jul 2009 03:52:51 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=07&amp;entry=entry090721-205251</comments>
		</item>
		<item>
			<title>Die IE6 DIE6!</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry090714-143925</link>
			<description><![CDATA[I look forward to the day IE6 is wiped from the face of the internet like doomsday cults await their rapturous glory. I have read rumours - and I tend to agree - that IE6 is costing the world economy millions of dollars - I would like to see the figure properly researched, it could be billions!<br /><br />Slowly we see big sites like YouTube phasing out IE6 support, slowly. Alas, I have client sites with 20% of visitors using IE6 so the end is not yet in sight...<br /><br /><a href="http://www.techcrunch.com/2009/07/14/youtube-will-be-next-to-kiss-ie6-support-goodbye/" target="_blank" >The Tech Crunch story</a><br /><br /><a href="http://ajaxian.com/archives/the-slow-death-of-ie-6-support-youtube-and-browser-placement" target="_blank" >What Ajaxian had to say about it</a><br /><br /><a href="http://idroppedie6.com/" target="_blank" >Join the list of sites not supporting IE6</a><br /><br />]]></description>
			<category>BugFeatures, Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry090714-143925</guid>
			<author>Dan</author>
			<pubDate>Tue, 14 Jul 2009 21:39:25 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=07&amp;entry=entry090714-143925</comments>
		</item>
		<item>
			<title>OnChange Radio Buttons - Symfony, Prototype and me</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry090712-151846</link>
			<description><![CDATA[Using Symfony&#039;s observe_field() on radio buttons will work only the first time the value is changed. If a user changes the values more than once the onchange event stops firing. To get around this you need to use the onclick event instead (as observer_field() uses onchange - and to support keyboard events you need to add an onkeyup handler as well).<br /><br />A solution was posted at <a href="http://symfonynerds.com/blog/?p=53" target="_blank" >Symfony nerds</a> in the comments suggesting a hardcoded onclick Ajax call in the radiobutton() method. I suggest using the remote_function() instead to simplify the code, avoid duplication and allow the helper to do the work. After all, that&#039;s what helpers are for...<br /><br /><pre>&lt;?php <br />echo radiobutton_tag(<br />  &quot;tt_product&quot;,<br />  1,<br />  true,<br />  &#039;onclick=toolPref(1)&#039;<br />); <br />?&gt;<br /><br />&lt;?php <br />   echo javascript_tag(&quot;<br />     function toolPref(val){<br />       &quot;.remote_function(array(<br />       &#039;url&#039;  =&gt; &#039;user/tallypref&#039;,<br />       &#039;with&#039; =&gt; &quot;&#039;tally_product=&#039;+val&quot;)).&quot;<br />     }&quot;<br />   );<br />  ?&gt;</pre>]]></description>
			<category>BugFeatures, Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry090712-151846</guid>
			<author>Dan</author>
			<pubDate>Sun, 12 Jul 2009 22:18:46 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=07&amp;entry=entry090712-151846</comments>
		</item>
		<item>
			<title>MySQL Replication - what to do when a slave fails</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry090707-221647</link>
			<description><![CDATA[Thought this was worth posting as it could save someone a lot of grief. I had a slave MySQL server stop updating without warning. A SELECT SLAVE STATUS showed there was an error due to an existing record. This would have occurred when a SELECT DATA INFILE populated 50,000 records and things went a little haywaire. The solution was quite simple:<br /><br />1. STOP SLAVE;<br />2. TRUNCATE TABLE table_name;<br />3. START SLAVE;<br /><br />This wipes out all data in the table with errors and allows the master to rebuild it from scratch. If you have foreign keys you might need to turn them off first...]]></description>
			<category>BugFeatures, Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry090707-221647</guid>
			<author>Dan</author>
			<pubDate>Wed, 08 Jul 2009 05:16:47 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=07&amp;entry=entry090707-221647</comments>
		</item>
		<item>
			<title>Using gmail SMTP with Swift Mailer</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry090705-230616</link>
			<description><![CDATA[Want your web app to send email from your gmail or google apps hosted email account? This way your messages are stored in your sent box. Here&#039;s how to do it with a failover to localhost (in case there is a problem with the gmail server or authentication)...<br /><pre><br />      try{<br />        $connection = new Swift_Connection_SMTP(<br />          sfConfig::get(&quot;app_gmail_host&quot;), <br />          Swift_Connection_SMTP::PORT_SECURE, <br />          Swift_Connection_SMTP::ENC_TLS<br />        ); <br />        $connection-&gt;setUsername(sfConfig::get(&quot;app_gmail_username&quot;));<br />        $connection-&gt;setPassword(sfConfig::get(&quot;app_gmail_password&quot;));<br />        $connection-&gt;attachAuthenticator(<br />          new Swift_Authenticator_PLAIN()<br />        );<br />        $mailer = new Swift($connection);<br />        $mailer-&gt;disconnect();<br />        echo &quot;Using gmail...&quot; . PHP_EOL;<br />        unset($mailer);<br />      } catch (Exception $e)    {<br />        echo &quot;Using localhost...&quot; . PHP_EOL;<br />        $connection = new Swift_Connection_NativeMail();      <br />      }<br />      <br />      //use our working mailer<br />      $mailer = new Swift($connection);<br /></pre><br />Note you need to disconnect, destroy and recreate your SMTP mailer or else gmail will accuse you of trying to change identity!]]></description>
			<category>BugFeatures, Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry090705-230616</guid>
			<author>Dan</author>
			<pubDate>Mon, 06 Jul 2009 06:06:16 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=07&amp;entry=entry090705-230616</comments>
		</item>
		<item>
			<title>Assign an environment to a subdomain in Symfony</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry090607-185507</link>
			<description><![CDATA[I have a couple of symfony sites that my clients like to test without messing up their data (go figure). By modifying the controller a little I can detect the subdomain from the URL and then switch the syfmony environment accordingly. Then, using a filter, I can put a demo stylesheet in place to make the site look different as well...<br /><br />By using this technique you can have exact copies of an application without duplicating a single file!<br /><br />Step 1 - Set up the subdomain<br /><br />Create a new subdomain in the DNS and configure apache to accept it as an alias for the live site.<br /><br /><pre>&lt;VirtualHost *:80&gt;<br />  ServerName site.com<br />  ServerAlias <a href="http://www.site.com" target="_blank" >www.site.com</a> demo.site.com<br />  DocumentRoot &quot;/var/www/&quot;<br />&lt;/VirtualHost&gt;</pre><br />Step 2 - Create a new database for the subdomain and configure the databases.yml file <br /><br /><pre><br />test:<br />  propel:<br />    class:          sfPropelDatabase<br />    param:<br />      dsn:          mysql://root:pass@localhost/live<br /><br />demo:<br />  propel:<br />    class:          sfPropelDatabase<br />    param:<br />      dsn:          mysql://root:pass@localhost/demo</pre><br />Step 3 - Modify the controller<br /><br />Symfony 1.0:<br /><br /><pre>//check for DEMO site<br />$env = &quot;live&quot;;<br />$debug = false;<br />$domain = $_SERVER[&#039;HTTP_HOST&#039;];<br />if (strpos($domain,&quot;demo&quot;) !== false){<br />  $env = &quot;demo&quot;; <br />  $debug = true;<br />}<br /><br />define(&#039;SF_ROOT_DIR&#039;,    realpath(dirname(__FILE__).&#039;/../..&#039;));<br />define(&#039;SF_APP&#039;,         &#039;my_app&#039;);<br />define(&#039;SF_ENVIRONMENT&#039;, $env);<br />define(&#039;SF_DEBUG&#039;,       $debug);</pre><br />Or in Symfony 1.2:<br /><br /><pre>$configuration = ProjectConfiguration::getApplicationConfiguration(&#039;my_app&#039;, $env, $debug);<br />sfContext::createInstance($configuration)-&gt;dispatch();<br /></pre><br />Step 4 - Create the filter<br /><br />My favourite bit - save it in your lib folder:<br /><br /><pre>&lt;?php<br /> <br />class demoFilter extends sfFilter<br />{<br />  public function execute ($filterChain)<br />  {   <br /><br />    if (SF_ENVIRONMENT == &quot;demo&quot; &amp;&amp; $this-&gt;isFirstCall())<br />    {<br />      $this-&gt;getContext()-&gt;getResponse()-&gt;addStylesheet(&#039;demo&#039;,&#039;last&#039;);<br />    }<br /> <br />    // execute next filter<br />    $filterChain-&gt;execute();<br />  }<br />}<br />?&gt;</pre><br />Step 5 - Add your stylesheet &amp; images etc<br /><br />The above filter adds demo.css to each page, so create the file and any images it requires and upload to your existing css and images folders and you&#039;re done.]]></description>
			<category>BugFeatures, Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry090607-185507</guid>
			<author>Dan</author>
			<pubDate>Mon, 08 Jun 2009 01:55:07 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=06&amp;entry=entry090607-185507</comments>
		</item>
		<item>
			<title>Working with MySQL replication in PHP</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry090525-190359</link>
			<description><![CDATA[I recently set up mySQL replication for a client and thought I&#039;d share this quick and easy way of distributing database load over a few machines.<br /><br />First, set up replication - it&#039;s very simple. Here&#039;s a simplified list of tasks based on the info at mysql.com:<br /><br /><a href="http://forums.gentoo.org/viewtopic.php?t=241123" target="_blank" >http://forums.gentoo.org/viewtopic.php?t=241123</a><br /><br />Then you need to adapt your site to use the master server for all write operations and distribute the read load across your slaves (and master if you like).<br /><br />My setup was moving from a single server to 2 servers. The primary server runs apache and the master database. The secondary server takes all the database read traffic (high CPU low bandwidth) and also serves up media files (low CPU high bandwidth).<br /><br />The way I set up the config for the databases was to allow for failover from the slave back onto the master. Now this site was built in 2003 and uses some very old PEAR classes (remember PEAR? ;-), but you  get the idea:<br /><br /><pre>$options = array(<br />&#039;debug&#039;       =&gt; 2,<br />&#039;portability&#039; =&gt; DB_PORTABILITY_ALL,<br />);<br /><br />$dsn = array(<br />&#039;phptype&#039;  =&gt; &#039;mysql&#039;,<br />&#039;username&#039; =&gt; def_user,<br />&#039;password&#039; =&gt; def_pass,<br />&#039;hostspec&#039; =&gt; def_host,<br />&#039;database&#039; =&gt; def_dbname,<br />);<br /><br />$db_master =&amp; DB::connect($dsn, $options);<br />if (PEAR::isError($db_master)) {<br />  showError($db_master-&gt;getMessage());<br />}<br />$db_master-&gt;setFetchMode(DB_FETCHMODE_OBJECT);<br /><br /><br />//use 2nd database server if available<br />//otherwise duplicate original as failover<br />if (defined(&#039;def_host2&#039;)){<br />  $dsn2 = array(<br />  &#039;phptype&#039;  =&gt; &#039;mysql&#039;,<br />  &#039;username&#039; =&gt; def_user2,<br />  &#039;password&#039; =&gt; def_pass2,<br />  &#039;hostspec&#039; =&gt; def_host2,<br />  &#039;database&#039; =&gt; def_dbname2,<br />  );<br />  $db =&amp; DB::connect($dsn2, $options);<br />  if (PEAR::isError($db)) {<br />    $db = $db_master;<br />  }else{<br />    $db-&gt;setFetchMode(DB_FETCHMODE_OBJECT);<br />  }<br />}else{<br />  $db = $db_master;<br />}</pre><br /><br />Then the next step was to modify the pages that included DELETE, UPDATE or INSERT queries to use $db_master instead of $db. The whole process took a little over an hour and the site was only offline for 20 minutes :)<br /><br /><br />]]></description>
			<category>BugFeatures, Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry090525-190359</guid>
			<author>Dan</author>
			<pubDate>Tue, 26 May 2009 02:03:59 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=05&amp;entry=entry090525-190359</comments>
		</item>
		<item>
			<title>Add Symfony CLI commands to PhpEd</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry090419-175132</link>
			<description><![CDATA[<img src="images/phpedcli.jpg" width="238" height="594" border="0" alt="" id="img_float_left" /><br /><br /><br />How to add a Symfony command to PhpEd:<br /><br />1. Menu: Tools-&gt;Settings<br />2. Go to Tools -&gt; Integration<br />3. Click &quot;Add Menu&quot;<br />4. Enter &quot;Symfony cc&quot; in menu name<br />5. Select &quot;Shell&quot; from Execute with list<br />6. Enter php @ProjRoot@/symfony cc in command line<br />7. Select checkbox next to &quot;Show this command in Workspace&quot; group<br />8. Deselect &quot;for files&quot; checkbox <br />9. Select &quot;for directories and projects&quot;<br /><br />Save and then try right-clicking on a project to run your command from the context menu.<br /><br />You can also direct the output of the command line back into PhpEd&#039;s log panel - this is a good way to ensure your CLI commands are executing without errors - as the windows command prompt has a tendency to flash and disappear before your eyes...]]></description>
			<category>Coding, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry090419-175132</guid>
			<author>Dan</author>
			<pubDate>Mon, 20 Apr 2009 00:51:32 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=04&amp;entry=entry090419-175132</comments>
		</item>
		<item>
			<title>Abort PHP cron job if another job is running</title>
			<link>http://bugfeatures.com/blog/index.php?entry=entry090402-031305</link>
			<description><![CDATA[This has come in handy for a couple of mail queues I have built and thought it was worth sharing. Basically it&#039;s a small check for PHP in the running system processes. If *any* PHP script is running (besides itself) we abort the script. It would be nice to know *which* script was running but I haven&#039;t managed to do that yet :)<br /><br /><pre>if (strtoupper(substr(PHP_OS, 0, 3)) === &#039;WIN&#039;) {<br />  //windows, can&#039;t do thread detection<br />  $threads = 1;<br />}else{<br />  //check for PHP PID and abort<br />  $pids = preg_split(&#039;/\s+/&#039;, `ps -o pid --no-heading -C php`);<br />  $threads = 0;<br />  foreach($pids as $pid) {<br />    if(is_numeric($pid)) {<br />      $threads++;<br />      echo &quot;Process &quot; . $pid.&quot; found\r\n&quot;;<br />    }<br />  } <br />}</pre><br />]]></description>
			<category>BugFeatures, sydphp</category>
			<guid isPermaLink="true">http://bugfeatures.com/blog/index.php?entry=entry090402-031305</guid>
			<author>Dan</author>
			<pubDate>Thu, 02 Apr 2009 10:13:05 GMT</pubDate>
			<comments>http://bugfeatures.com/blog/comments.php?y=09&amp;m=04&amp;entry=entry090402-031305</comments>
		</item>
	</channel>
</rss>
