Tag error:   ->  Textpattern Notice: Page template default does not contain a txp:article tag  on line 464
Netxus Foundries: PHP

Nothing really new since there is already a firefox extension which does just the same but I also wanted support other browsers.

So this bookmarklet toggles the cookie Xdebug uses to flag if it should debug the running script or not.

Toggle Xdebug

Drag it to your bookmarks (or favourites or whatever is called in your browser) toolbar so it’s easy to access. Assign it a keyboard shortcut if your browser has that function.

By default it’ll prompt for the idekey to be used by Xdebug, it’s a good idea to modify the idekey variable at the start of the bookmarklet’s source with your idekey or with ‘default’ if you’re not using a DBGp proxy.

My current IDE of choice, Komodo, brings native support for the DBGp protocol to debug source code either locally or from a remote server. It has however some issues with its path mapping implementation.

Since it’s not the first time I stumbled upon this kind of issue (my previous Eclipse PDT experience was troublesome too) I finally tried to solve it. The solution is an intercepting proxy server which listen for connection from the debugger to modify the messages it sends to the IDE with custom path mappings.

To define the path mappings we use a file with a set of paths on each line. The path tuples are separated by the => operator, placing the path understood by the debugger on the left side and on the right the path as understood by the IDE. The file looks like this.


  # Mapping for projects
  file:///var/www/projects => file:///c:/documents%20and%20settings/drslump/projects
  # this one is for my subdomains
  file:///var/www/subdomains => file:///c:/subdomains

The path mappings are case insensitive to avoid problems on Windows.

To run the path mapper server we just need to call it from a terminal like so:

  php5 dbgp-mapper.php -i 192.168.0.5 -m dbgp.map

The -i option specifies the IP or host of the computer where the IDE is running, while the -m option points to the file where the path mappings are defined. There are also other options which you can check by seeing the help using the -h argument.

By default it will bind itself to the port 9000, which is DBGp default one, so if you are running this on the same computer as your IDE you will have to change the port either in this tool or on your IDE.

The source is available via this subversion repository

Update: It seems that Komodo uri mapping works properly after version 4.2.0. In my case I was having problems but after rebooting the system it started to work properly.

This weekend I found some time to work in a new project. In the last post I wrote about streaming video for Flash players, I went thru some different options for that task and one of them was the HTTP progressive download of FLV files.

There is an interesting idea to make seeking possible when using a simple HTTP download (I think Google Video does just this), it has been already mentioned in a few places.

There are some tools already which can inject the needed metadata into an flv file, namely, FLVTool2 and flvmdi. The ffmpeg project can create FLV files and there seem to be already patches to support the creation of metadata entries too.

Over the years I’ve come to love to develop readers for obscure (not standard neither well documented) file formats, it’s quite a challenge to do it but since the file format spec is not public you have a very good excuse when bugs are found :)

So I’ve started to work on a system to serve video (and also audio only) files to Flash based players with support for seeking. The idea is to just limit ourselves to use PHP and Flash technologies, no shell scripts or compiled programs, since they are difficult to get running on most shared hosts.

Right now the FLV analyzing library is finnished and the sample metadata extraction tool can process a 140Mb FLV in just 1.3 seconds on my cheap DreamHost test system.

So take a look at the project page, right now I’m looking for an Action Script guru to help out with the development of the player. I’ve tried to get my brains around Flash but I haven’t got much success :(

By the way, all this work will be used for an upcoming project I’m planning. Am I the only one who think that current web photo gallery systems suck?

Sep 11, 03:38 AM 22 comments

Quite so often a web developer starts thinking what’s wrong with the guys who make the rules of how Internet works. And if we dig a bit in the problem, we can many times see that the ‘non-sense’ is there not because the specs were flawed but because the popular implementations didn’t follow completely the spec.

The ‘non-sense’ I’ve been thinking about lately is why the hell we use the ampersand [&] to separate the arguments in a URL. My problem with using the & as separator is that in todays world of XML crazyness, the ampersand must be handled with care.
It seems that the specs said that any character could be used after the initial query char mark [?] but the [#] which marks the start of the fragment. In the current specs for URI/URL syntaxis they recommend the semicolon [;]

So the decision to choose the ampersand as the defacto standard was taken by the guys who took the lead with the CGI scripts and the dynamic web content generation.

Since the specs recommend the use of the semicolon I thought “ok, no problem, I’ll just use it from now on” with a grin in my face. If you are thinking the same keep reading because it’s not that easy. My server-side language of choice right now is PHP, so I build up a simple test script to check out if the semicolon worked ok, but it didn’t.
After consulting php.net for a bit more of information I found the solution, there are two ini directives which define the argument separators, one for input and the other for output (I guess this last one is used to manage the session id without cookies).

arg_separator.input and arg_separator.output are the names of the directives. The first one takes a string as argument and every char in that string becomes an argument separator, the second one defines what will PHP use when building URLs as separator.

The bad news is that the default value is just the ampersand [&]. The good news is that both directives can be defined per directory, so we can put them in our .htaccess in our shared hosts. If you control the host just edit the php.ini file. Logically we can’t modify them with ini_set() because the URI is already processed when our script starts execution (see below for a solution to this).


php_value arg_separator.input "&;"
php_value arg_separator.output ";"

Note that I left both: the ampersand [&] and the semicolor [;] as valid argument separators, I’m far too used to the ampersand and I don’t want to expend my whole life debugging an application just because I forgot that I decided that the semicolon was a better suit :)

Even if we can’t use the ini_set() function to modify the PHP behaviour for this, we can still make use of a different argument separator by processing the $_GET and $_REQUEST arrays and splitting them by our preffererd argument separator. The following code snippet show do it but please don’t use it in production environments, it’s probably flawed!

php:
  1. // first join the PHP splitted arguments
  2. $get = '';
  3. foreach ($_GET as $k=>$v)
  4. {
  5.     $get.= $k . '=' . $v . ';';
  6.     // remove from the arrays, we'll add the correct ones later
  7.     unset($_GET[$k]);
  8.     unset($_REQUEST[$k]);
  9. }
  10.         // split the query string using our desired char
  11. $args = split(';', $get);
  12.         // process the query string and rebuild the _GET and _REQUEST array
  13. foreach ($args as $arg)
  14. {
  15.     // now separate the key from the value and assign them to the arrays
  16.     list($k,$v) = explode('=', $arg);
  17.     if (! empty($k))
  18.     {
  19.         $_GET[ $k ] = $v;
  20.         $_REQUEST[ $k ] = $v;
  21.     }
  22. }

Changing the argument separator does not only help out when working with XML based technologies but also to give your site URLs a geeky look ;)

Our billing software runs on Windows platforms and uses ODBC to store its data. Recently we called the makers of that software since we wanted to extract some Bussiness Intelligence from our records.

It turned out that they where going to release a new program for that purpose. We asked to evaluate the beta version and to my surprise it just created an Microsft Excel file with a dynamic table report.

So I worked on the needed SQL query to extract the records we wanted and exported the data into a CSV to be loaded in an Excel worksheet with a dynamic table report. It worked ok, however the sales and marketting people didn’t want to contact a technician every time they needed the reports to be updated.

The problem was that our application server is a Debian box and since we didn’t have to much time to expend on this, we looked for the easiest solution. I found ODBC Socket Server which seems unmanteined but the last version works ok on our Windows Server 2003 machine. It’s a service which acts as a proxy for ODBC databases in the local machine, so they can be accessed from other machines using a socket connection and a very simple XML grammar.
Once everything was in place I wrote a simple PHP program to fetch the needed records (a few thousend rows). To my surprise the available PHP libraries to interact with ODBC Socket Server didn’t perform too well. They stored the full response in memory and then build up an array, since we need a few thousend rows, the amount of memory required was too high for our little Debian system.

The following code is a pretty simple set of classes to work with ODBC Socket Server. They fetch the data on demand, using a SAX type xml parser to discard the already processed records.
It’s not a really thought of or tested solution, however I’m making it public since it’s far better than existing solutions.

php:
  1. <?php
  2. /*
  3.         The ODBCSock class is an utility object to operate with the ODBC Socket Server
  4.         from http://odbcsock.sourceforge.net
  5.        
  6.         It's compatible with ODBC Socket Server's XML formats 0 and 2. In our tests
  7.         xml format 2 is significally faster than the others.
  8.         Should run on PHP4 and PHP5, however it's only been tested on PHP5
  9.        
  10.         This code is copyright (c) 2006 Netxus Foundries
  11.         v1.0 28-May-2006 : Ivan -DrSlump- Montes <imontes@netxus.es>
  12.        
  13.         This program is free software; you can redistribute it and/or modify
  14.   it under the terms of the GNU General Public License as published by
  15.   the Free Software Foundation; either version 2 of the License, or
  16.   (at your option) any later version.
  17.         This program is distributed in the hope that it will be useful, but
  18.         WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  19.         or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  20.         for more details.
  21.         You should have received a copy of the GNU General Public License along
  22.         with this program; if not, write to the Free Software Foundation, Inc.,
  23.         51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  24. */
  25.  
  26. class ODBCSock {
  27.         var $handle = null;
  28.         var $server = 'localhost';
  29.         var $port = '9628';
  30.         var $username = '';
  31.         var $password = '';
  32.         var $database = '';
  33.        
  34.         function ODBCSock( $server, $user, $passwd, $database = false )
  35.         {
  36.                 $server = explode(':', $server);
  37.                 if (count($server) > 1) $this->port = array_pop( $server );
  38.                 else $this->port = 9628;
  39.                 $this->server = implode(':', $server);
  40.                
  41.                 $this->handle = fsockopen( $this->server, $this->port, $errNo, $errStr, 30 );
  42.                 if ( ! $this->handle )
  43.                 {
  44.                         return null;
  45.                 }
  46.                
  47.                 $this->username = $user;
  48.                 $this->password = $passwd;
  49.                
  50.                 if ($database !== false)
  51.                         $this->selectDB( $database );
  52.         }
  53.  
  54.  
  55.         function selectDB( $database )
  56.         {
  57.                 $this->database = $database;
  58.         }
  59.  
  60.  
  61.         function & query( $query )
  62.         {
  63.                 $pl = array();
  64.                 $pl[] = '<?xml version="1.0"?>';
  65.                 $pl[] = '<request>';
  66.                 $pl[] = '<connectionstring>DSN=' . HTMLSpecialChars($this->database) . ';UID=' . HTMLSpecialChars($this->username) . ';PWD=' . HTMLSpecialChars($this->password) . ';</connectionstring>';
  67.                 $pl[] = '<sql>' . HTMLSpecialChars( $query ) . '</sql>';
  68.                 $pl[] = '</request>';
  69.                
  70.                 fwrite( $this->handle, implode("\r\n", $pl) );
  71.                
  72.                
  73.                 $rs = new ODBCSock_Result( $this );
  74.                 if ($rs->sax->error) return FALSE;
  75.                 else return $rs;
  76.         }
  77. }
  78.  
  79.  
  80. class ODBCSock_Result {
  81.         var $rows = array();
  82.         var $conn;
  83.         var $lastRow = 0;
  84.        
  85.         function ODBCSock_Result( &$conn )
  86.         {
  87.                 $this->conn =& $conn;      
  88.                
  89.                 $this->sax = new ODBCSock_SAX( $this );
  90.                 $this->parser = xml_parser_create();
  91.                 xml_set_object( $this->parser, $this->sax );
  92.                 xml_parser_set_option( $this->parser, XML_OPTION_CASE_FOLDING, false );
  93.                
  94.                 xml_set_element_handler( $this->parser, 'startElement', 'endElement' );
  95.                 xml_set_character_data_handler( $this->parser, 'content' );
  96.                
  97.                 while ( $this->_read() )
  98.                 {
  99.                         if ($this->sax->error || $this->sax->parsing)
  100.                                 break;
  101.                 }
  102.         }
  103.        
  104.         function _eof()
  105.         {
  106.                 return feof($this->conn->handle);
  107.         }
  108.        
  109.         function _read()
  110.         {
  111.                 xml_parse( $this->parser, fread( $this->conn->handle, 4096 ), feof($this->conn->handle) );
  112.         }
  113.        
  114.         function addRow( $row )
  115.         {
  116.                 $this->rows[] = $row;
  117.         }
  118.        
  119.        
  120.         function fetch( $row = false )
  121.         {
  122.                 if ($row === false)
  123.                 {
  124.                         $row = $this->lastRow;
  125.                         $this->lastRow++;
  126.                 }
  127.                 else
  128.                 {
  129.                         $this->lastRow = $row+1;
  130.                 }
  131.                
  132.                 do {
  133.                         $this->_read();
  134.                        
  135.                         if (isset($this->rows[$row]))
  136.                                 return $this->rows[$row];
  137.                                
  138.                 } while ( !$this->_eof() );
  139.                
  140.         }
  141.        
  142. }
  143.  
  144. class ODBCSock_SAX {
  145.        
  146.         var $error = false;
  147.         var $parsing = false;
  148.        
  149.         var $curElement = '';
  150.         var $curColumn = '';
  151.         var $curColumnIdx = 0;
  152.         var $curRowIdx = 0;
  153.        
  154.         var $rs = null;
  155.         var $row;       
  156.         var $labels = array();
  157.        
  158.         function ODBCSock_SAX   ( &$rs )
  159.         {
  160.                 $this->rs =& $rs;
  161.         }
  162.        
  163.         function startElement( $parser, $name, $attrs )
  164.         {
  165.                 $this->curElement = $name;
  166.                
  167.                 if ( $name == 'result' )
  168.                 {
  169.                         if ($attrs['state'] == 'success')
  170.                                 $this->parsing = true;     
  171.                         else
  172.                                 $this->error = true;
  173.                 }
  174.                 else if ( $name == 'row' )
  175.                 {
  176.                         $this->row = array();
  177.                         $this->curColumnIdx = 0;
  178.                 }
  179.                 else if ($name == 'column')
  180.                 {
  181.                         if ($attrs['name'])
  182.                         {
  183.                                 if ($curRowIdx == 0)
  184.                                         $this->labels[] = $attrs['name'];
  185.                                        
  186.                                 $this->curColumn = $attrs['name'];
  187.                         }
  188.                         else
  189.                                 $this->curColumn = $this->labels[$this->curColumnIdx];
  190.                                
  191.                         $this->row[ $this->curColumn ] = '';
  192.                         $this->curColumnIdx++;
  193.                 }
  194.         }
  195.        
  196.         function endElement( $parser, $name )
  197.         {
  198.                 if ( $name == 'row' )
  199.                 {
  200.                         $this->rs->addRow( $this->row );
  201.                         $this->curRowIdx++;
  202.                 }              
  203.         }
  204.        
  205.         function content( $parser, $data )
  206.         {
  207.                 if ($this->curElement == 'column')
  208.                 {
  209.                         $this->row[ $this->curColumn ] .= $data;
  210.                 }
  211.         }
  212. }
  213. ?>