Back to index

awl  0.53
classBrowser.php
Go to the documentation of this file.
00001 <?php
00017 require_once("AWLUtilities.php");
00018 
00022 $BrowserCurrentRow = (object) array();
00023 
00024 
00025 
00032 class BrowserColumn
00033 {
00034   var $Field;
00035   var $Header;
00036   var $Format;
00037   var $Sql;
00038   var $Align;
00039   var $Class;
00040   var $Type;
00041   var $Translatable;
00042   var $Hook;
00043   var $current_row;
00044 
00069   function BrowserColumn( $field, $header="", $align="", $format="", $sql="", $class="", $datatype="", $hook=null ) {
00070     $this->Field  = $field;
00071     $this->Sql    = $sql;
00072     $this->Header = $header;
00073     $this->Format = $format;
00074     $this->Class  = $class;
00075     $this->Align  = $align;
00076     $this->Type   = $datatype;
00077     $this->Translatable = false;
00078     $this->Hook   = $hook;
00079   }
00080 
00086   function GetTarget() {
00087     if ( $this->Sql == "" ) return $this->Field;
00088     return "$this->Sql AS $this->Field";
00089   }
00090 
00104   function RenderHeader( $order_field, $order_direction, $browser_array_key=0, $forced_order=false ) {
00105     global $c;
00106     if ( $this->Align == "" ) $this->Align = "left";
00107     $html = '<th class="'.$this->Align.'" '. ($this->Class == "" ? "" : "class=\"$this->Class\"") . '>';
00108 
00109     $direction = 'A';
00110     $image = "";
00111     if ( !$forced_order && $order_field == $this->Field ) {
00112       if ( strtoupper( substr( $order_direction, 0, 1) ) == 'A' ) {
00113         $image = 'down';
00114         $direction = 'D';
00115       }
00116       else {
00117         $image = 'up';
00118       }
00119       $image = "<img class=\"order\" src=\"$c->images/$image.gif\" alt=\"$image\" />";
00120     }
00121     if ( !isset($browser_array_key) || $browser_array_key == '' ) $browser_array_key = 0;
00122     if ( !$forced_order ) $html .= '<a href="'.replace_uri_params( $_SERVER['REQUEST_URI'], array( "o[$browser_array_key]" => $this->Field, "d[$browser_array_key]" => $direction ) ).'" class="order">';
00123     $html .= ($this->Header == "" ? $this->Field : $this->Header);
00124     if ( !$forced_order ) $html .= "$image</a>";
00125     $html .= "</th>\n";
00126     return $html;
00127   }
00128 
00129   function SetTranslatable() {
00130     $this->Translatable = true;
00131   }
00132 
00133   function RenderValue( $value, $extraclass = "" ) {
00134     global $session;
00135 
00136     if ( $this->Type == 'date' || $this->Type == 'timestamp') {
00137       $value = $session->FormattedDate( $value, $this->Type );
00138     }
00139 
00140     if ( $this->Hook && function_exists($this->Hook) ) {
00141       dbg_error_log( "Browser", ":Browser: Hook for $this->Hook on column $this->Field");
00142       $value = call_user_func( $this->Hook, $value, $this->Field, $this->current_row );
00143     }
00144 
00145     if ( $this->Translatable ) {
00146       $value = translate($value);
00147     }
00148 
00149     $value = str_replace( "\n", "<br />", $value );
00150     if ( substr(strtolower($this->Format),0,3) == "<td" ) {
00151       $html = sprintf($this->Format,$value);
00152     }
00153     else {
00154       // These quite probably don't work.  The CSS standard for multiple classes is 'class="a b c"' but is lightly
00155       // implemented according to some web references.  Perhaps modern browsers are better?
00156       $class = $this->Align . ($this->Class == "" ? "" : " $this->Class") . ($extraclass == "" ? "" : " $extraclass");
00157       if ( $class != "" ) $class = ' class="'.$class.'"';
00158       $html = sprintf('<td%s>',$class);
00159       $html .= ($this->Format == "" ? $value : sprintf($this->Format,$value,$value));
00160       $html .= "</td>\n";
00161     }
00162     return $html;
00163   }
00164 }
00165 
00166 
00174 class Browser
00175 {
00176   var $Title;
00177   var $SubTitle;
00178   var $FieldNames;
00179   var $Columns;
00180   var $HiddenColumns;
00181   var $Joins;
00182   var $Where;
00183   var $Distinct;
00184   var $Union;
00185   var $Order;
00186   var $OrderField;
00187   var $OrderDirection;
00188   var $OrderBrowserKey;
00189   var $ForcedOrder;
00190   var $Grouping;
00191   var $Limit;
00192   var $Offset;
00193   var $Query;
00194   var $BeginRow;
00195   var $CloseRow;
00196   var $BeginRowArgs;
00197   var $Totals;
00198   var $TotalFuncs;
00199   var $ExtraRows;
00200   var $match_column;
00201   var $match_value;
00202   var $match_function;
00203   var $DivOpen;
00204   var $DivClose;
00205 
00211   function Browser( $title = "" ) {
00212     global $c;
00213     $this->Title = $title;
00214     $this->SubTitle = "";
00215     $this->Distinct = "";
00216     $this->Order = "";
00217     $this->Limit = "";
00218     $this->Offset = "";
00219     $this->BeginRow = "<tr class=\"row%d\">\n";
00220     $this->CloseRow = "</tr>\n";
00221     $this->BeginRowArgs = array('#even');
00222     $this->Totals = array();
00223     $this->Columns = array();
00224     $this->HiddenColumns = array();
00225     $this->FieldNames = array();
00226     $this->DivOpen = '<div id="browser">';
00227     $this->DivClose = '</div>';
00228     $this->ForcedOrder = false;
00229     dbg_error_log( "Browser", ":Browser: New browser called $title");
00230   }
00231 
00257   function AddColumn( $field, $header="", $align="", $format="", $sql="", $class="", $datatype="", $hook=null ) {
00258     $this->Columns[] = new BrowserColumn( $field, $header, $align, $format, $sql, $class, $datatype, $hook );
00259     $this->FieldNames[$field] = count($this->Columns) - 1;
00260   }
00261 
00272   function AddHidden( $field, $sql="" ) {
00273     $this->HiddenColumns[] = new BrowserColumn( $field, "", "", "", $sql );
00274     $this->FieldNames[$field] = count($this->Columns) - 1;
00275   }
00276 
00286   function SetTitle( $new_title ) {
00287     $this->Title = $new_title;
00288   }
00289 
00290 
00297   function Title( $new_title = null ) {
00298     if ( isset($new_title) ) $this->Title = $new_title;
00299     return $this->Title;
00300   }
00301 
00302 
00308   function SetTranslatable( $column_list ) {
00309     $top = count($this->Columns);
00310     for( $i=0; $i < $top; $i++ ) {
00311       dbg_error_log( "Browser", "Comparing %s with column name list", $this->Columns[$i]->Field);
00312       if ( in_array($this->Columns[$i]->Field,$column_list) ) $this->Columns[$i]->SetTranslatable();
00313     }
00314     $top = count($this->HiddenColumns);
00315     for( $i=0; $i < $top; $i++ ) {
00316       dbg_error_log( "Browser", "Comparing %s with column name list", $this->HiddenColumns[$i]->Field);
00317       if ( in_array($this->HiddenColumns[$i]->Field,$column_list) ) $this->HiddenColumns[$i]->SetTranslatable();
00318     }
00319   }
00320 
00326   function SetSubTitle( $sub_title ) {
00327     $this->SubTitle = $sub_title;
00328   }
00329 
00336   function SetDiv( $open_div, $close_div ) {
00337     $this->DivOpen = $open_div;
00338     $this->DivClose = $close_div;
00339   }
00340 
00350   function SetJoins( $join_list ) {
00351     $this->Joins = $join_list;
00352   }
00353 
00363   function SetUnion( $union_select ) {
00364     $this->Union = $union_select;
00365   }
00366 
00374   function SetWhere( $where_clause ) {
00375     $this->Where = $where_clause;
00376   }
00377 
00385   function SetDistinct( $distinct ) {
00386     $this->Distinct = "DISTINCT ".$distinct;
00387   }
00388 
00396   function SetLimit( $limit_n ) {
00397     $this->Limit = "LIMIT ".intval($limit_n);
00398   }
00399 
00407   function SetOffset( $offset_n ) {
00408     $this->Offset = "OFFSET ".intval($offset_n);
00409   }
00410 
00420   function MoreWhere( $operator, $more_where ) {
00421     if ( $this->Where == "" ) {
00422       $this->Where = $more_where;
00423       return;
00424     }
00425     $this->Where = "$this->Where $operator $more_where";
00426   }
00427 
00433   function AndWhere( $more_where ) {
00434     $this->MoreWhere("AND",$more_where);
00435   }
00436 
00442   function OrWhere( $more_where ) {
00443     $this->MoreWhere("OR",$more_where);
00444   }
00445 
00446   function AddGrouping( $field, $browser_array_key=0 ) {
00447     if ( $this->Grouping == "" )
00448       $this->Grouping = "GROUP BY ";
00449     else
00450       $this->Grouping .= ", ";
00451 
00452     $this->Grouping .= clean_string($field);
00453   }
00454 
00455 
00471   function AddOrder( $field, $direction, $browser_array_key=0, $secondary=0 ) {
00472     $field = check_by_regex($field,'/^[^\'"!\\\\()\[\]|*\/{}&%@~;:?<>]+$/');
00473     if ( ! isset($this->FieldNames[$field]) ) return;
00474 
00475     if ( !isset($this->Order) || $this->Order == "" )
00476       $this->Order = "ORDER BY ";
00477     else
00478       $this->Order .= ", ";
00479 
00480     if ( $secondary == 0 ) {
00481       $this->OrderField = $field;
00482       $this->OrderBrowserKey = $browser_array_key;
00483     }
00484     $this->Order .= $field;
00485 
00486     if ( preg_match( '/^A/i', $direction) ) {
00487       $this->Order .= " ASC";
00488       if ( $secondary == 0)
00489         $this->OrderDirection = 'A';
00490     }
00491     else {
00492       $this->Order .= " DESC";
00493       if ( $secondary == 0)
00494         $this->OrderDirection = 'D';
00495     }
00496   }
00497 
00498 
00505   function ForceOrder( $field, $direction ) {
00506     $field = clean_string($field);
00507     if ( ! isset($this->FieldNames[$field]) ) return;
00508 
00509     if ( $this->Order == "" )
00510       $this->Order = "ORDER BY ";
00511     else
00512       $this->Order .= ", ";
00513 
00514     $this->Order .= $field;
00515 
00516     if ( preg_match( '/^A/i', $direction) ) {
00517       $this->Order .= " ASC";
00518     }
00519     else {
00520       $this->Order .= " DESC";
00521     }
00522 
00523     $this->ForcedOrder = true;
00524   }
00525 
00526 
00532   function SetOrdering( $default_fld=null, $default_dir='A' , $browser_array_key=0 ) {
00533     if ( isset( $_GET['o'][$browser_array_key] ) && isset($_GET['d'][$browser_array_key] ) ) {
00534       $this->AddOrder( $_GET['o'][$browser_array_key], $_GET['d'][$browser_array_key], $browser_array_key );
00535     }
00536     else {
00537       if ( ! isset($default_fld) ) $default_fld = $this->Columns[0];
00538       $this->AddOrder( $default_fld, $default_dir, $browser_array_key );
00539     }
00540   }
00541 
00542 
00554   function AddTotal( $column_name, $total_function = false ) {
00555     $this->Totals[$column_name] = 0;
00556     if ( $total_function != false ) {
00557       $this->TotalFuncs[$column_name] = $total_function;
00558     }
00559   }
00560 
00561 
00567   function GetTotal( $column_name ) {
00568     return $this->Totals[$column_name];
00569   }
00570 
00571 
00594   function RowFormat( $beginrow, $closerow, $rowargs )
00595   {
00596     $argc = func_num_args();
00597     $this->BeginRow = func_get_arg(0);
00598     $this->CloseRow = func_get_arg(1);
00599 
00600     $this->BeginRowArgs = array();
00601     for( $i=2; $i < $argc; $i++ ) {
00602       $this->BeginRowArgs[] = func_get_arg($i);
00603     }
00604   }
00605 
00606 
00615   function DoQuery() {
00616     $target_fields = "";
00617     foreach( $this->Columns AS $k => $column ) {
00618       if ( $target_fields != "" ) $target_fields .= ", ";
00619       $target_fields .= $column->GetTarget();
00620     }
00621     if ( isset($this->HiddenColumns) ) {
00622       foreach( $this->HiddenColumns AS $k => $column ) {
00623         if ( $target_fields != "" ) $target_fields .= ", ";
00624         $target_fields .= $column->GetTarget();
00625       }
00626     }
00627     $where_clause = ((isset($this->Where) && $this->Where != "") ? "WHERE $this->Where" : "" );
00628     $sql = sprintf( "SELECT %s %s FROM %s %s %s ", $this->Distinct, $target_fields,
00629                  $this->Joins, $where_clause, $this->Grouping );
00630     if ( "$this->Union" != "" ) {
00631       $sql .= "UNION $this->Union ";
00632     }
00633     $sql .= $this->Order . ' ' . $this->Limit . ' ' . $this->Offset;
00634     $this->Query = new AwlQuery( $sql );
00635     return $this->Query->Exec("Browse:$this->Title:DoQuery");
00636   }
00637 
00638 
00644   function AddRow( $column_values ) {
00645     if ( !isset($this->ExtraRows) || typeof($this->ExtraRows) != 'array' ) $this->ExtraRows = array();
00646     $this->ExtraRows[] = &$column_values;
00647   }
00648 
00649 
00657   function MatchedRow( $column, $value, $function ) {
00658     $this->match_column = $column;
00659     $this->match_value  = $value;
00660     $this->match_function = $function;
00661   }
00662 
00663 
00673   function ValueReplacement($matches)
00674   {
00675     // as usual: $matches[0] is the complete match
00676     // $matches[1] the match for the first subpattern
00677     // enclosed in '##...##' and so on
00678 
00679     $field_name = $matches[1];
00680     if ( !isset($this->current_row->{$field_name}) && substr($field_name,0,4) == "URL:" ) {
00681       $field_name = substr($field_name,4);
00682       $replacement = urlencode($this->current_row->{$field_name});
00683     }
00684     else {
00685       $replacement = (isset($this->current_row->{$field_name}) ? $this->current_row->{$field_name} : '');
00686     }
00687     dbg_error_log( "Browser", ":ValueReplacement: Replacing %s with %s", $field_name, $replacement);
00688     return $replacement;
00689   }
00690 
00691 
00702   function Render( $title_tag = null, $subtitle_tag = null ) {
00703     global $c, $BrowserCurrentRow;
00704 
00705     if ( !isset($this->Query) ) $this->DoQuery();  // Ensure the query gets run before we render!
00706 
00707     dbg_error_log( "Browser", ":Render: browser $this->Title");
00708     $html = $this->DivOpen;
00709     if ( $this->Title != "" ) {
00710       if ( !isset($title_tag) ) $title_tag = 'h1';
00711       $html .= "<$title_tag>$this->Title</$title_tag>\n";
00712     }
00713     if ( $this->SubTitle != "" ) {
00714       if ( !isset($subtitle_tag) ) $subtitle_tag = 'h2';
00715       $html .= "<$subtitle_tag>$this->SubTitle</$subtitle_tag>\n";
00716     }
00717 
00718     $html .= "<table id=\"browse_table\">\n";
00719     $html .= "<thead><tr class=\"header\">\n";
00720     foreach( $this->Columns AS $k => $column ) {
00721       $html .= $column->RenderHeader( $this->OrderField, $this->OrderDirection, $this->OrderBrowserKey, $this->ForcedOrder );
00722     }
00723     $html .= "</tr></thead>\n<tbody>";
00724 
00725     $rowanswers = array();
00726     while( $BrowserCurrentRow = $this->Query->Fetch() ) {
00727 
00728       // Work out the answers to any stuff that may be being substituted into the row start
00730       foreach( $this->BeginRowArgs AS $k => $fld ) {
00731         if ( isset($BrowserCurrentRow->{$fld}) ) {
00732           $rowanswers[$k] = $BrowserCurrentRow->{$fld};
00733         }
00734         else {
00735           switch( $fld ) {
00736             case '#even':
00737               $rowanswers[$k] = ($this->Query->rownum() % 2);
00738               break;
00739             default:
00740               $rowanswers[$k] = $fld;
00741           }
00742         }
00743       }
00744       // Start the row
00745       $row_html = vsprintf( preg_replace("/#@even@#/", ($this->Query->rownum() % 2), $this->BeginRow), $rowanswers);
00746 
00747       if ( isset($this->match_column) && isset($this->match_value) && $BrowserCurrentRow->{$this->match_column} == $this->match_value ) {
00748         $row_html .= call_user_func( $this->match_function, $BrowserCurrentRow );
00749       }
00750       else {
00751         // Each column
00752         foreach( $this->Columns AS $k => $column ) {
00753           $row_html .= $column->RenderValue( (isset($BrowserCurrentRow->{$column->Field})?$BrowserCurrentRow->{$column->Field}:'') );
00754           if ( isset($this->Totals[$column->Field]) ) {
00755             if ( isset($this->TotalFuncs[$column->Field]) && function_exists($this->TotalFuncs[$column->Field]) ) {
00756               // Run the amount through the callback function  $floatval = my_function( $row, $fieldval );
00757               $this->Totals[$column->Field] += $this->TotalFuncs[$column->Field]( $BrowserCurrentRow, $BrowserCurrentRow->{$column->Field} );
00758             }
00759             else {
00760               // Just add the amount
00761               $this->Totals[$column->Field] += doubleval( preg_replace( '/[^0-9.-]/', '', $BrowserCurrentRow->{$column->Field} ));
00762             }
00763           }
00764         }
00765       }
00766 
00767       // Finish the row
00768       $row_html .= preg_replace("/#@even@#/", ($this->Query->rownum() % 2), $this->CloseRow);
00769       $this->current_row = $BrowserCurrentRow;
00770       $html .= preg_replace_callback("/##([^#]+)##/", array( &$this, "ValueReplacement"), $row_html );
00771     }
00772 
00773     if ( count($this->Totals) > 0 ) {
00774       $BrowserCurrentRow = (object) "";
00775       $row_html = "<tr class=\"totals\">\n";
00776       foreach( $this->Columns AS $k => $column ) {
00777         if ( isset($this->Totals[$column->Field]) ) {
00778           $row_html .= $column->RenderValue( $this->Totals[$column->Field], "totals" );
00779         }
00780         else {
00781           $row_html .= $column->RenderValue( "" );
00782         }
00783       }
00784       $row_html .= "</tr>\n";
00785       $this->current_row = $BrowserCurrentRow;
00786       $html .= preg_replace_callback("/##([^#]+)##/", array( &$this, "ValueReplacement"), $row_html );
00787     }
00788 
00789 
00790     if ( count($this->ExtraRows) > 0 ) {
00791       foreach( $this->ExtraRows AS $k => $v ) {
00792         $BrowserCurrentRow = (object) $v;
00793         // Work out the answers to any stuff that may be being substituted into the row start
00794         foreach( $this->BeginRowArgs AS $k => $fld ) {
00795           if ( isset( $BrowserCurrentRow->{$fld} ) ) {
00796             $rowanswers[$k] = $BrowserCurrentRow->{$fld};
00797           }
00798           else {
00799             switch( $fld ) {
00800               case '#even':
00801                 $rowanswers[$k] = ($this->Query->rownum() % 2);
00802                 break;
00803               default:
00804                 $rowanswers[$k] = $fld;
00805             }
00806           }
00807         }
00808 
00809         // Start the row
00810         $row_html = vsprintf( preg_replace("/#@even@#/", ($this->Query->rownum() % 2), $this->BeginRow), $rowanswers);
00811 
00812         if ( isset($this->match_column) && isset($this->match_value) && $BrowserCurrentRow->{$this->match_column} == $this->match_value ) {
00813           $row_html .= call_user_func( $this->match_function, $BrowserCurrentRow );
00814         }
00815         else {
00816           // Each column
00817           foreach( $this->Columns AS $k => $column ) {
00818             $row_html .= $column->RenderValue( (isset($BrowserCurrentRow->{$column->Field}) ? $BrowserCurrentRow->{$column->Field} : '') );
00819           }
00820         }
00821 
00822         // Finish the row
00823         $row_html .= preg_replace("/#@even@#/", ($this->Query->rownum() % 2), $this->CloseRow);
00824         $this->current_row = $BrowserCurrentRow;
00825         $html .= preg_replace_callback("/##([^#]+)##/", array( &$this, "ValueReplacement"), $row_html );
00826       }
00827     }
00828 
00829     $html .= "</tbody>\n</table>\n";
00830     $html .= $this->DivClose;
00831 
00832     return $html;
00833   }
00834 
00835 }