* @copyright Catalyst IT Ltd, Morphoss Ltd * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 */ require_once("DataUpdate.php"); require_once("DataEntry.php"); /** * A class for the fields in the editor * @package apms */ class EditorField { var $Field; var $Sql; var $Value; var $Attributes; var $LookupSql; var $OptionList; function __construct( $field, $sql="", $lookup_sql="" ) { global $session; $this->Field = $field; $this->Sql = $sql; $this->LookupSql = $lookup_sql; $this->Attributes = array(); $session->Log("DBG: New field '%s' SQL='%s', Lookup='%s'", $field, $sql, $lookup_sql ); } function Set($value) { $this->Value = $value; } function SetSql( $sql ) { $this->Sql = $sql; } function SetLookup( $lookup_sql ) { $this->LookupSql = $lookup_sql; } function SetOptionList( $options, $current = null, $parameters = null) { if ( gettype($options) == 'array' ) { $this->OptionList = ''; if ( is_array($parameters) ) { if ( isset($parameters['maxwidth']) ) $maxwidth = max(4,intval($parameters['maxwidth'])); if ( isset($parameters['translate']) ) $translate = true; } foreach( $options AS $k => $v ) { if (is_array($current)) { $selected = ( ( in_array($k,$current,true) || in_array($v,$current,true)) ? ' selected="selected"' : '' ); } else { $selected = ( ( "$k" == "$current" || "$v" == "$current" ) ? ' selected="selected"' : '' ); } if ( isset($translate) ) $v = translate( $v ); if ( isset($maxwidth) ) $v = substr( $v, 0, $maxwidth); $this->OptionList .= ""; } } else { $this->OptionList = $options; } } function GetTarget() { if ( $this->Sql == "" ) return $this->Field; return "$this->Sql AS $this->Field"; } function AddAttribute( $k, $v ) { $this->Attributes[$k] = $v; } function RenderAttributes() { $attributes = ""; if ( count($this->Attributes) == 0 ) return $attributes; foreach( $this->Attributes AS $k => $v ) { $attributes .= " $k=\"" . str_replace('"', '\\"', $v) . '"'; } return $attributes; } } /** * The class for the Editor form in full * @package apms */ class Editor { var $Title; var $Action; var $Fields; var $OrderedFields; var $BaseTable; var $Joins; var $Where; var $NewWhere; var $Order; var $Limit; var $Query; var $Template; var $RecordAvailable; var $Record; var $SubmitName; var $Id; function __construct( $title = "", $fields = null ) { global $c, $session, $form_id_increment; $this->Title = $title; $this->Order = ""; $this->Limit = ""; $this->Template = ""; $this->RecordAvailable = false; $this->SubmitName = 'submit'; $form_id_increment = (isset($form_id_increment)? ++$form_id_increment : 1); $this->Id = 'editor_'.$form_id_increment; if ( isset($fields) ) { if ( is_array($fields) ) { foreach( $fields AS $k => $v ) { $this->AddField($v); } } else if ( is_string($fields) ) { // We've been given a table name, so get all fields for it. $this->BaseTable = $fields; $field_list = get_fields($fields); foreach( $field_list AS $k => $v ) { $this->AddField($k); } } } dbg_error_log("classEditor", "DBG: New editor called $title"); } function &AddField( $field, $sql="", $lookup_sql="" ) { $this->Fields[$field] = new EditorField( $field, $sql, $lookup_sql ); $this->OrderedFields[] = $field; return $this->Fields[$field]; } function SetSql( $field, $sql ) { $this->Fields[$field]->SetSql( $sql ); } function SetLookup( $field, $lookup_sql ) { if (is_object($this->Fields[$field])) { $this->Fields[$field]->SetLookup( $lookup_sql ); } } function Value( $value_field_name ) { if ( !isset($this->Record->{$value_field_name}) ) return null; return $this->Record->{$value_field_name}; } function Assign( $value_field_name, $new_value ) { $this->Record->{$value_field_name} = $new_value; } function Id( $id = null ) { if ( isset($id) ) $this->Id = preg_replace( '#[^a-z0-9_+-]#', '', $id); return $this->Id; } function SetOptionList( $field, $options, $current = null, $parameters = null) { $this->Fields[$field]->SetOptionList( $options, $current, $parameters ); } function AddAttribute( $field, $k, $v ) { $this->Fields[$field]->AddAttribute($k,$v); } function SetBaseTable( $base_table ) { $this->BaseTable = $base_table; } function SetJoins( $join_list ) { $this->Joins = $join_list; } /** * Accessor for the Title for the browse, which could set the title also. * * @param string $new_title The new title for the browser * @return string The current title for the browser */ function Title( $new_title = null ) { if ( isset($new_title) ) $this->Title = $new_title; return $this->Title; } function SetSubmitName( $new_submit ) { $this->SubmitName = $new_submit; } function IsSubmit() { return isset($_POST[$this->SubmitName]); } function IsUpdate() { $is_update = $this->Available(); if ( isset( $_POST['_editor_action']) && isset( $_POST['_editor_action'][$this->Id]) ) { $is_update = ( $_POST['_editor_action'][$this->Id] == 'update' ); dbg_error_log("ERROR", "Checking update: %s => %d", $_POST['_editor_action'][$this->Id], $is_update ); } return $is_update; } function SetWhere( $where_clause ) { $this->Where = $where_clause; } function WhereNewRecord( $where_clause ) { $this->NewWhere = $where_clause; } function MoreWhere( $operator, $more_where ) { if ( $this->Where == "" ) { $this->Where = $more_where; return; } $this->Where = "$this->Where $operator $more_where"; } function AndWhere( $more_where ) { $this->MoreWhere("AND",$more_where); } function OrWhere( $more_where ) { $this->MoreWhere("OR",$more_where); } function SetTemplate( $template ) { $this->Template = $template; } function Layout( $template ) { if ( strstr( $template, '##form##' ) === false && stristr( $template, 'Template = $template; } function Available( ) { return $this->RecordAvailable; } function SetRecord( $row ) { $this->Record = $row; $this->RecordAvailable = is_object($this->Record); return $this->Record; } /** * Set some particular values to the ones from the array. * * @param array $values An array of fieldname / value pairs */ function Initialise( $values ) { $this->RecordAvailable = false; foreach( $values AS $fname => $value ) { $this->Record->{$fname} = $value; } } function GetRecord( $where = "" ) { global $session; $target_fields = ""; foreach( $this->Fields AS $k => $column ) { if ( $target_fields != "" ) $target_fields .= ", "; $target_fields .= $column->GetTarget(); } if ( $where == "" ) $where = $this->Where; $sql = sprintf( "SELECT %s FROM %s %s WHERE %s %s %s", $target_fields, $this->BaseTable, $this->Joins, $where, $this->Order, $this->Limit); $this->Query = new PgQuery( $sql ); $session->Log("DBG: EditorGetQry: %s", $sql ); if ( $this->Query->Exec("Browse:$this->Title:DoQuery") ) { $this->Record = $this->Query->Fetch(); $this->RecordAvailable = is_object($this->Record); } if ( !$this->RecordAvailable ) { $this->Record = (object) array(); } return $this->Record; } /** * Replace parts into the form template. * @param array $matches The matches found which preg_replace_callback is calling us for. * @return string What we want to replace this match with. */ function ReplaceEditorPart($matches) { global $session; // $matches[0] is the complete match switch( $matches[0] ) { case "##form##": /** @todo It might be nice to construct a form ID */ return sprintf('
', $this->Id); case "##submit##": $action = ( $this->RecordAvailable ? 'update' : 'insert' ); $submittype = ($this->RecordAvailable ? 'Apply Changes' : 'Create'); return sprintf('', $this->Id, $action, $this->SubmitName, $submittype ); } // $matches[1] the match for the first subpattern // enclosed in '(...)' and so on $field_name = $matches[1]; $what_part = $matches[3]; $part3 = (isset($matches[5]) ? $matches[5] : null); $value_field_name = $field_name; if ( substr($field_name,0,4) == 'xxxx' ) { // Sometimes we will prepend 'xxxx' to the field name so that the field // name differs from the column name in the database. We also remove it // when it's submitted. $value_field_name = substr($field_name,4); } $attributes = ""; if ( isset($this->Fields[$field_name]) && is_object($this->Fields[$field_name]) ) { $field = $this->Fields[$field_name]; $attributes = $field->RenderAttributes(); } $field_value = (isset($this->Record->{$value_field_name}) ? $this->Record->{$value_field_name} : null); switch( $what_part ) { case "options": $currval = $part3; if ( ! isset($currval) && isset($field_value) ) $currval = $field_value; if ( isset($field->OptionList) && $field->OptionList != "" ) { $option_list = $field->OptionList; } else { $session->Log("DBG: Current=%s, OptionQuery: %s", $currval, $field->LookupSql ); $opt_qry = new PgQuery( $field->LookupSql ); $option_list = $opt_qry->BuildOptionList($currval, "FieldOptions: $field_name" ); $field->OptionList = $option_list; } return $option_list; case "select": $currval = $part3; if ( ! isset($currval) && isset($field_value) ) $currval = $field_value; if ( isset($field->OptionList) && $field->OptionList != "" ) { $option_list = $field->OptionList; } else { $session->Log("DBG: Current=%s, OptionQuery: %s", $currval, $field->LookupSql ); $opt_qry = new PgQuery( $field->LookupSql ); $option_list = $opt_qry->BuildOptionList($currval, "FieldOptions: $field_name" ); $field->OptionList = $option_list; } return ""; case "checkbox": switch ( $field_value ) { case 'f': case 'off': case 'false': case '': case '0': $checked = ""; break; default: $checked = " CHECKED"; } return ""; case "input": $size = (isset($part3) ? $part3 : 6); return ""; case "file": $size = (isset($part3) ? $part3 : 30); return ""; case "money": $size = (isset($part3) ? $part3 : 8); return ""; case "date": $size = (isset($part3) ? $part3 : 10); return ""; case "textarea": list( $cols, $rows ) = split( 'x', $part3); return ""; case "hidden": return sprintf( "", htmlspecialchars($field_value) ); case "password": return sprintf( "", htmlspecialchars($part3) ); case "encval": case "enc": return htmlspecialchars($field_value); case "submit": $action = ( $this->RecordAvailable ? 'update' : 'insert' ); return sprintf('', $this->Id, $action, $this->SubmitName, $value_field_name ); default: return str_replace( "\n", "
", $field_value ); } } /** * Render the templated component. The heavy lifting is done by the callback... */ function Render( $title_tag = null ) { dbg_error_log("classEditor", "Rendering editor $this->Title" ); if ( $this->Template == "" ) $this->DefaultTemplate(); $html = sprintf('
', $this->Id); if ( isset($this->Title) && $this->Title != "" ) { if ( !isset($title_tag) ) $title_tag = 'h1'; $html = "<$title_tag>$this->Title\n"; } // Stuff like "##fieldname.part## gets converted to the appropriate value $replaced = preg_replace_callback("/##([^#.]+)(\.([^#.]+))?(\.([^#.]+))?##/", array(&$this, "ReplaceEditorPart"), $this->Template ); $html .= $replaced; $html .= '
'; return $html; } /** * Write the record * @param boolean $is_update Tell the write whether it's an update or insert. Hopefully it * should be able to figure it out though. */ function Write( $is_update = null ) { global $c, $component; dbg_error_log("classEditor", "DBG: Writing editor $this->Title"); if ( !isset($is_update) ) { if ( isset( $_POST['_editor_action']) && isset( $_POST['_editor_action'][$this->Id]) ) { $is_update = ( $_POST['_editor_action'][$this->Id] == 'update' ); } else { /** @todo Our old approach will not work for translation. We need to have a hidden field * containing the submittype. Probably we should add placeholders like ##form##, ##script## etc. * which the editor can use for internal purposes. */ // Then we dvine the action by looking at the submit button value... $is_update = preg_match( '/(save|update|apply)/i', $_POST[$this->SubmitName] ); dbg_error_log("ERROR", $_SERVER['REQUEST_URI']. " is using a deprecated method for controlling insert/update" ); } } $this->Action = ( $is_update ? "update" : "create" ); $qry = new PgQuery( sql_from_post( $this->Action, $this->BaseTable, "WHERE ".$this->Where ) ); if ( !$qry->Exec("Editor::Write") ) { $c->messages[] = "ERROR: $qry->errorstring"; return 0; } if ( $this->Action == "create" && isset($this->NewWhere) ) { $this->GetRecord($this->NewWhere); } else { $this->GetRecord($this->Where); } return $this->Record; } }