<?php

    
/*
        
        PHPDDF (PHP Data Display Functions) version 0.1
        (c) 2005 Toby A Inkster
        
        Licence: GNU *Lesser* General Public Licence
        http://www.gnu.org/licenses/lgpl.html
        
        This library is free software; you can redistribute it and/or
        modify it under the terms of the GNU Lesser General Public
        License as published by the Free Software Foundation; either
        version 2.1 of the License, or (at your option) any later version.

        This library is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
        Lesser General Public License for more details.
    
        You should have received a copy of the GNU Lesser General Public
        License along with this library; if not, write to the Free Software
        Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
        
        ============================================================
        
        display_table - Displays a searchable, sortable table.
        
        USAGE:
            display_table (COLUMN_INFO, DBTABLE, DBCONN, OPTIONS)
        
        COLUMN_INFO:
            An array of arrays. The main array's keys are the
            names of the columns of an SQL table. The values assigned
            to these keys are themselves arrays of options that
            apply to that column.
            
            The following are valid options:
            
                Behavioural Options
                -------------------
            
                displayable - set to 0 to hide this column
                from the display; set to 1 to show; default 1.
                
                label - required for any displayable column, this
                label is used as a column header, and for a label
                in a search form.
                
                type - data type of the column; takes the values
                'id', 'number', 'string', 'url', 'email' or 'image'.
                There should only be one 'id' column. Default is
                'number'.
                
                sortable - if set to 1, allows the user to sort
                the data by this column. Default is 0.
                
                searchable - if set to 1, allows the user to filter
                results by this column. Default is 1.
                
                searchpolicy - specifies how searches should behave.
                Possible values are 'exact' for exact matches, 'partial'
                for substring searching, 'head' and 'tail' for beginning
                and ending substrings and 'numerical' to allow a
                variety of numerical searches, including greater-than
                and less-than. Default is 'exact'. All searches (even
                'exact') are case-insensitive.

                Display Options
                ---------------
                
                imgwidth - in pixels, fixes a width for images. Only
                applies to data of type 'image'.
                
                align - 'left', 'right' or 'center' to align data
                within a column. 'auto' chooses an appropriate alignment
                based on data type. Default 'auto'.
                
                format - sprintf-style formatting code for the
                data within this column.
                
                function - callback function for more flexible display
                of the data in this column. The function will be called
                with the following arguments:
                
                    * connection to database
                    * row ID
                    * data value
                    
                function and format are mutually exclusive.
                            
        DBTABLE:
            String. The SQL table to display.
            
        DBCONN:
            Resource. An open SQL connection to query. display_table
            supports both MySQL and PostgreSQL. MySQL is assumed, but
            if the variable $DATATYPE is set to 'pgsql', PostgreSQL will
            be used.
            
        OPTIONS:
            Array of strings. Used to set other options for the table,
            including:
                
                showsql - set to '1' to show the SQL query itself.
                Default 0.
                
                caption - caption for the data.
                
        EXAMPLE:
            
            $cols = array(
                "idnum"  => array("type"=>"id", "label=>"ID#"),
                "name"   => array("type"=>"string", "label"=>"Name",
                          "searchable"=>1,
                          "searchpolicy"=>"head"),
                "url"     => array("type"=>"url", label=>"URL"),
                "password" => array("displayable"=>0)
            );
            $table = 'users';
            $db = pg_connect("dbname=userinfo");
            $DATATYPE = 'pgsql';
            $opt = array("caption"=>"User info","showsql"=>1);
            display_table($cols,$table,$db,$opt);


    */
    
    
function display_table ($cols$table$db$opt)
    {

        global 
$DATATYPE;
        
        
$idcol '';

        
// GENERATE QUERY
    
        
$sortorder $_REQUEST['sortorder'];
        
$cond = array("(1=1)");
        
$limit = ($_REQUEST['limit'] && $_REQUEST['limit']>0)?$_REQUEST['limit']:10;
        
$offset = ($_REQUEST['offset'] && $_REQUEST['offset']>=0)?$_REQUEST['offset']:0;;
        
$sqloffset += ($offset $limit);
        
        foreach (
$cols as $k=>$v)
        {
            if (!isset(
$sortorder) && $cols[$k]["sortable"]==1)
            {
                
$sortorder $k;
            }
            
            if (
$cols[$k]["type"]=='id'$idcol=$k;
            
            if (
$cols[$k]["searchable"]==
                
&& isset($_REQUEST['search:'.$k])
                && 
$_REQUEST['search:'.$k]!='')
            {
                
$like = ($DATATYPE=='pgsql')?'ILIKE':'LIKE';
                
                
$val $_REQUEST['search:'.$k];
                
                if (
$cols[$k]["searchpolicy"]=="partial")
                {
                    
array_push($cond"($k $like '%$val%')");
                }
                elseif (
$cols[$k]["searchpolicy"]=="head")
                {
                    
array_push($cond"($k $like '$val%')");
                }
                elseif (
$cols[$k]["searchpolicy"]=="tail")
                {
                    
array_push($cond"($k $like '$val%')");
                }
                elseif (
$cols[$k]["searchpolicy"]=="numerical")
                {
                    if (
substr($val,0,1)=='=')
                    {
                        
$val substr($val,1);
                        
array_push($cond"($k='$val')");
                    }
                    elseif (
substr($val,0,1)=='<')
                    {
                        
$val substr($val,1);
                        
array_push($cond"($k<'$val')");
                    }
                    elseif (
substr($val,0,1)=='>')
                    {
                        
$val substr($val,1);
                        
array_push($cond"($k>'$val')");
                    }
                    elseif (
substr($val,0,1)=='!')
                    {
                        
$val substr($val,1);
                        
array_push($cond"(NOT $k='$val')");
                    }
                    elseif (
substr($val,0,1)=='~')
                    {
                        
$val substr($val,1);
                        
$val1 0.8 $val;
                        
$val2 1.2 $val;
                        
array_push($cond"($k>'$val1' AND $k<'$val2')");
                    }
                    else 
                    {
                        
array_push($cond"($k='$val')");
                    }
                    
                }
                else 
// ($cols[$k]["searchpolicy"]=="exact")
                
{
                    
array_push($cond"($k $like '$val')");
                }
            }
        }
        
$condstr join(' AND '$cond);
        
        if (
$DATATYPE=='pgsql')
            
$query "SELECT * FROM $table WHERE ($condstr) ORDER BY $sortorder LIMIT $limit OFFSET $sqloffset;";
        else
            
$query "SELECT * FROM $table WHERE ($condstr) ORDER BY $sortorder LIMIT $sqloffset$limit;";
        
        
// DISPLAY QUERY
        
if ($opt["showsql"]==1)
        {
            print 
"<div style=\"margin:1em 0;font-size:0.8em;font-weight:bold;font-style:italic;padding:0.5em;border:1px solid;background:white url('sql.jpeg') no-repeat scroll top right\">\n";
            if (
$DATATYPE=='pgsql')
                print 
"SELECT *<br>FROM $table<br>WHERE ($condstr)<br>ORDER BY $sortorder<br>LIMIT $limit OFFSET $sqloffset;";
            else
                print 
"SELECT *<br>FROM $table<br>WHERE ($condstr)<br>ORDER BY $sortorder<br>LIMIT $sqloffset$limit;";
            print 
"</div>\n";
        }

        
// DISPLAY FORM
        
print "<form action=\"${_SERVER['PHP_SELF']}\" method=\"get\">\n<div>\n";
        foreach (
$cols as $k=>$v)
        {
            if (
$cols[$k]["searchable"]==1)
            {
                print 
"<label><b>".$cols[$k]['label'].":</b><br>\n";
                print 
"<input name=\"search:$k\" value=\"".$_REQUEST['search:'.$k]."\"><br>\n";
                print 
"<small>";
                if (
$cols[$k]["searchpolicy"]=='partial')
                    print 
"Partial string search. Matches any part of the text.";
                elseif (
$cols[$k]["searchpolicy"]=='head')
                    print 
"Head search. Matches the beginning part of the text.";
                elseif (
$cols[$k]["searchpolicy"]=='tail')
                    print 
"Tail search. Matches the end part of the text.";
                elseif (
$cols[$k]["searchpolicy"]=='numerical')
                    print 
"Numeric search. Where X is a number, use '&lt;X' to match results less than X; use '>X' to match results greater than X; use '!X' to match any results except X; use '=X' to match exactly X and use '~X' to match results roughly X.";
                else 
// ($cols[$k]["searchpolicy"]=='exact')
                    
print "Exact matches only.";
                print 
"</small></label><br>\n&nbsp;<br>\n";
            }
        }
        print 
"<label><b>Sort Order:</b><br>\n";
        print 
"<select name=\"sortorder\">\n";
        foreach (
$cols as $k=>$v)
        {
            if (
$cols[$k]["sortable"]==1)
            {
                
$l $cols[$k]["label"];
                if (
$k==$sortorder) print "<option selected value=\"$k\">$l\n";
                else print 
"<option value=\"$k\">$l\n";
            }
        }
        print 
"</select></label><br>\n&nbsp;<br>\n";
        print 
"<label><b>Number of Results to Display:</b><br>\n";
        print 
"<input name=\"limit\" value=\"$limit\">\n";
        print 
"</label><br>\n&nbsp;<br>\n";
        print 
"<input type=\"submit\" value=\"Submit Query\">\n";
        print 
"</div>\n</form>\n";
                
        
// DISPLAY RESULTS TABLE
        
$dc = array();
        print 
"<table border=\"1\">\n";
        
        if (isset(
$opt['caption']))
            print 
'<caption>'.$opt['caption']."</caption>\n";
            
        foreach (
$cols as $k=>$v)
        {
            if (
$cols[$k]["displayable"]!=|| !isset($cols[$k]["displayable"]))
            {
                if (isset(
$v["align"]) && $v["align"]!='auto')
                    
$a $v["align"];
                elseif (
$v["type"]=="number" || $v["type"]=="id" || !isset($v["type"]))
                    
$a 'right';
                elseif (
$v["type"]=="image")
                    
$a 'center';
                else
                    
$a 'left';
                print 
"<col align=\"$a\">\t";
            }
        }
        print 
"<thead>\n";
        print 
"<tr>";
        
$ncols 0;
        foreach (
$cols as $k=>$v)
        {
            if (
$cols[$k]["displayable"]!=|| !isset($cols[$k]["displayable"]))
            {
                
$ncols++;
                
array_push($dc,$k);
                
$l $v["label"];
                print 
"<th align=\"center\">$l</th>\t";
            }
        }
        print 
"</tr>\n";
        print 
"</thead>\n";
        print 
"<tfoot>\n";
        print 
"<tr>\n";
        print 
"<td align=\"center\" colspan=\"$ncols\">\n";
        
$str '';

        foreach (
$_REQUEST as $k=>$v)
            if (
$k!='offset')
                
$str .= $k.'='.$v.'&';
                
        
$prevstr htmlspecialchars($_SERVER['PHP_SELF'] . '?' $str 'offset=' . ($offset-1));
        
$nextstr htmlspecialchars($_SERVER['PHP_SELF'] . '?' $str 'offset=' . ($offset+1));
        print 
"<a style=\"float:left;text-decoration:none\" href=\"$prevstr\">&lt;Prev</a>\n";
        print 
"Page ".($offset+1)." of Results\n";
        print 
"<a style=\"float:right;text-decoration:none\" href=\"$nextstr\">Next&gt;</a>\n";
        print 
"</td>\n";
        print 
"</tr>\n";
        print 
"</tfoot>\n";
        print 
"<tbody>\n";
        
        if (
$DATATYPE=='pgsql')
            
$rset pg_query($db$query);
        else    
            
$rset mysql_query($query$db);
        
        while (
$r = ($DATATYPE=='pgsql')?pg_fetch_array($rset):mysql_fetch_array($rset))
        {
            print 
"<tr>\n";
            foreach (
$dc as $c)
            {
                
$v $r[$c];
                
                if (isset(
$cols[$c]["function"]))
                {
                    print 
"<td>";
                    print 
call_user_func($cols[$c]["function"], $db$r[$idcol], $v);
                    print 
"</td>";
                }
                
                else 
                {
                    if (isset(
$cols[$c]["format"]))
                        
$v sprintf($cols[$c]["format"], $v);
                    
                    
$v htmlspecialchars($v);
                    
                    if (
$v==''||!isset($v)) $v '&nbsp;';
                    
                    if (
$cols[$c]["type"]=='image' && isset($cols[$c]["imgwidth"]))
                        print 
"<td><img src=\"$v\" alt=\"$v\" width=\"".$cols[$c]["imgwidth"]."\"></td>\t";
                    elseif (
$cols[$c]["type"]=='image')
                        print 
"<td><img src=\"$v\" alt=\"$v\"></td>\t";
                    elseif (
$cols[$c]["type"]=='email')
                        print 
"<td><a href=\"mailto:$v\">$v</a></td>\t";
                    elseif (
$cols[$c]["type"]=='url')
                        print 
"<td><a href=\"$v\">$v</a></td>\t";
                    else
                        print 
"<td>$v</td>\t";
                }
            }
            print 
"</tr>\n";
        }
        
        print 
"</tbody>\n";
        print 
"</table>\n";
    }

?>