Make the submenus marginally wider.
[capital-apms.git] / inc / classEditor.php
blob14d19651770157bdf4c0a2dd5d6f53c9bc2488a0
1 <?php
2 /**
3 * Class for editing a record using a templated form.
5 * @package apms
6 * @subpackage classEditor
7 * @author Andrew McMillan <andrew@mcmillan.net.nz>
8 * @copyright Catalyst IT Ltd, Morphoss Ltd <http://www.morphoss.com/>
9 * @license http://gnu.org/copyleft/gpl.html GNU GPL v2
12 require_once("DataUpdate.php");
13 require_once("DataEntry.php");
15 /**
16 * A class for the fields in the editor
17 * @package apms
19 class EditorField
21 var $Field;
22 var $Sql;
23 var $Value;
24 var $Attributes;
25 var $LookupSql;
26 var $OptionList;
28 function EditorField( $field, $sql="", $lookup_sql="" ) {
29 global $session;
30 $this->Field = $field;
31 $this->Sql = $sql;
32 $this->LookupSql = $lookup_sql;
33 $this->Attributes = array();
34 $session->Log("DBG: New field '%s' SQL='%s', Lookup='%s'", $field, $sql, $lookup_sql );
37 function Set($value) {
38 $this->Value = $value;
41 function SetSql( $sql ) {
42 $this->Sql = $sql;
45 function SetLookup( $lookup_sql ) {
46 $this->LookupSql = $lookup_sql;
49 function SetOptionList( $options, $current = null, $parameters = null) {
50 if ( gettype($options) == 'array' ) {
51 $this->OptionList = '';
53 if ( is_array($parameters) ) {
54 if ( isset($parameters['maxwidth']) ) $maxwidth = max(4,intval($parameters['maxwidth']));
55 if ( isset($parameters['translate']) ) $translate = true;
58 foreach( $options AS $k => $v ) {
59 if (is_array($current)) {
60 $selected = ( ( in_array($k,$current,true) || in_array($v,$current,true)) ? ' selected="selected"' : '' );
62 else {
63 $selected = ( ( "$k" == "$current" || "$v" == "$current" ) ? ' selected="selected"' : '' );
65 if ( isset($translate) ) $v = translate( $v );
66 if ( isset($maxwidth) ) $v = substr( $v, 0, $maxwidth);
67 $this->OptionList .= "<option value=\"".htmlspecialchars($k)."\"$selected>".htmlspecialchars($v)."</option>";
70 else {
71 $this->OptionList = $options;
75 function GetTarget() {
76 if ( $this->Sql == "" ) return $this->Field;
77 return "$this->Sql AS $this->Field";
80 function AddAttribute( $k, $v ) {
81 $this->Attributes[$k] = $v;
84 function RenderAttributes() {
85 $attributes = "";
86 if ( count($this->Attributes) == 0 ) return $attributes;
87 foreach( $this->Attributes AS $k => $v ) {
88 $attributes .= " $k=\"" . str_replace('"', '\\"', $v) . '"';
90 return $attributes;
96 /**
97 * The class for the Editor form in full
98 * @package apms
100 class Editor
102 var $Title;
103 var $Action;
104 var $Fields;
105 var $OrderedFields;
106 var $BaseTable;
107 var $Joins;
108 var $Where;
109 var $NewWhere;
110 var $Order;
111 var $Limit;
112 var $Query;
113 var $Template;
114 var $RecordAvailable;
115 var $Record;
116 var $SubmitName;
117 var $Id;
119 function Editor( $title = "", $fields = null ) {
120 global $c, $session, $form_id_increment;
121 $this->Title = $title;
122 $this->Order = "";
123 $this->Limit = "";
124 $this->Template = "";
125 $this->RecordAvailable = false;
126 $this->SubmitName = 'submit';
127 $form_id_increment = (isset($form_id_increment)? ++$form_id_increment : 1);
128 $this->Id = 'editor_'.$form_id_increment;
130 if ( isset($fields) ) {
131 if ( is_array($fields) ) {
132 foreach( $fields AS $k => $v ) {
133 $this->AddField($v);
136 else if ( is_string($fields) ) {
137 // We've been given a table name, so get all fields for it.
138 $this->BaseTable = $fields;
139 $field_list = get_fields($fields);
140 foreach( $field_list AS $k => $v ) {
141 $this->AddField($k);
145 dbg_error_log("classEditor", "DBG: New editor called $title");
148 function &AddField( $field, $sql="", $lookup_sql="" ) {
149 $this->Fields[$field] = new EditorField( $field, $sql, $lookup_sql );
150 $this->OrderedFields[] = $field;
151 return $this->Fields[$field];
154 function SetSql( $field, $sql ) {
155 $this->Fields[$field]->SetSql( $sql );
158 function SetLookup( $field, $lookup_sql ) {
159 $this->Fields[$field]->SetLookup( $lookup_sql );
162 function Value( $value_field_name ) {
163 if ( !isset($this->Record->{$value_field_name}) ) return null;
164 return $this->Record->{$value_field_name};
167 function Assign( $value_field_name, $new_value ) {
168 $this->Record->{$value_field_name} = $new_value;
171 function Id( $id = null ) {
172 if ( isset($id) ) $this->Id = preg_replace( '#[^a-z0-9_+-]#', '', $id);
173 return $this->Id;
176 function SetOptionList( $field, $options, $current = null, $parameters = null) {
177 $this->Fields[$field]->SetOptionList( $options, $current, $parameters );
180 function AddAttribute( $field, $k, $v ) {
181 $this->Fields[$field]->AddAttribute($k,$v);
185 function SetBaseTable( $base_table ) {
186 $this->BaseTable = $base_table;
189 function SetJoins( $join_list ) {
190 $this->Joins = $join_list;
193 function SetTitle( $new_title ) {
194 $this->Title = $new_title;
197 function SetSubmitName( $new_submit ) {
198 $this->SubmitName = $new_submit;
201 function IsSubmit() {
202 return isset($_POST[$this->SubmitName]);
205 function IsUpdate() {
206 $is_update = $this->Available();
207 if ( isset( $_POST['_editor_action']) && isset( $_POST['_editor_action'][$this->Id]) ) {
208 $is_update = ( $_POST['_editor_action'][$this->Id] == 'update' );
209 dbg_error_log("ERROR", "Checking update: %s => %d", $_POST['_editor_action'][$this->Id], $is_update );
211 return $is_update;
214 function SetWhere( $where_clause ) {
215 $this->Where = $where_clause;
218 function WhereNewRecord( $where_clause ) {
219 $this->NewWhere = $where_clause;
222 function MoreWhere( $operator, $more_where ) {
223 if ( $this->Where == "" ) {
224 $this->Where = $more_where;
225 return;
227 $this->Where = "$this->Where $operator $more_where";
230 function AndWhere( $more_where ) {
231 $this->MoreWhere("AND",$more_where);
234 function OrWhere( $more_where ) {
235 $this->MoreWhere("OR",$more_where);
238 function SetTemplate( $template ) {
239 $this->Template = $template;
242 function Layout( $template ) {
243 if ( strstr( $template, '##form##' ) === false && stristr( $template, '<form' ) === false ) $template = '##form##' . $template;
244 if ( stristr( $template, '</form' ) === false ) $template .= '</form>';
245 $this->Template = $template;
248 function Available( ) {
249 return $this->RecordAvailable;
252 function SetRecord( $row ) {
253 $this->Record = $row;
254 $this->RecordAvailable = is_object($this->Record);
255 return $this->Record;
259 * Set some particular values to the ones from the array.
261 * @param array $values An array of fieldname / value pairs
263 function Initialise( $values ) {
264 $this->RecordAvailable = false;
265 foreach( $values AS $fname => $value ) {
266 $this->Record->{$fname} = $value;
271 function GetRecord( $where = "" ) {
272 global $session;
273 $target_fields = "";
274 foreach( $this->Fields AS $k => $column ) {
275 if ( $target_fields != "" ) $target_fields .= ", ";
276 $target_fields .= $column->GetTarget();
278 if ( $where == "" ) $where = $this->Where;
279 $sql = sprintf( "SELECT %s FROM %s %s WHERE %s %s %s",
280 $target_fields, $this->BaseTable, $this->Joins, $where, $this->Order, $this->Limit);
281 $this->Query = new PgQuery( $sql );
282 $session->Log("DBG: EditorGetQry: %s", $sql );
283 if ( $this->Query->Exec("Browse:$this->Title:DoQuery") ) {
284 $this->Record = $this->Query->Fetch();
285 $this->RecordAvailable = is_object($this->Record);
287 if ( !$this->RecordAvailable ) {
288 $this->Record = (object) array();
290 return $this->Record;
295 * Replace parts into the form template.
296 * @param array $matches The matches found which preg_replace_callback is calling us for.
297 * @return string What we want to replace this match with.
299 function ReplaceEditorPart($matches)
301 global $session;
303 // $matches[0] is the complete match
304 switch( $matches[0] ) {
305 case "##form##": /** @todo It might be nice to construct a form ID */
306 return sprintf('<form method="POST" enctype="multipart/form-data" class="editor" id="%s">', $this->Id);
307 case "##submit##":
308 $action = ( $this->RecordAvailable ? 'update' : 'insert' );
309 $submittype = ($this->RecordAvailable ? 'Apply Changes' : 'Create');
310 return sprintf('<input type="hidden" name="_editor_action[%s]" value="%s"><input type="submit" class="submit" name="%s" value="%s">',
311 $this->Id, $action, $this->SubmitName, $submittype );
314 // $matches[1] the match for the first subpattern
315 // enclosed in '(...)' and so on
316 $field_name = $matches[1];
317 $what_part = $matches[3];
318 $part3 = (isset($matches[5]) ? $matches[5] : null);
320 $value_field_name = $field_name;
321 if ( substr($field_name,0,4) == 'xxxx' ) {
322 // Sometimes we will prepend 'xxxx' to the field name so that the field
323 // name differs from the column name in the database. We also remove it
324 // when it's submitted.
325 $value_field_name = substr($field_name,4);
328 $attributes = "";
329 if ( isset($this->Fields[$field_name]) && is_object($this->Fields[$field_name]) ) {
330 $field = $this->Fields[$field_name];
331 $attributes = $field->RenderAttributes();
333 $field_value = (isset($this->Record->{$value_field_name}) ? $this->Record->{$value_field_name} : null);
335 switch( $what_part ) {
336 case "options":
337 $currval = $part3;
338 if ( ! isset($currval) && isset($field_value) )
339 $currval = $field_value;
340 if ( isset($field->OptionList) && $field->OptionList != "" ) {
341 $option_list = $field->OptionList;
343 else {
344 $session->Log("DBG: Current=%s, OptionQuery: %s", $currval, $field->LookupSql );
345 $opt_qry = new PgQuery( $field->LookupSql );
346 $option_list = $opt_qry->BuildOptionList($currval, "FieldOptions: $field_name" );
347 $field->OptionList = $option_list;
349 return $option_list;
350 case "select":
351 $currval = $part3;
352 if ( ! isset($currval) && isset($field_value) )
353 $currval = $field_value;
354 if ( isset($field->OptionList) && $field->OptionList != "" ) {
355 $option_list = $field->OptionList;
357 else {
358 $session->Log("DBG: Current=%s, OptionQuery: %s", $currval, $field->LookupSql );
359 $opt_qry = new PgQuery( $field->LookupSql );
360 $option_list = $opt_qry->BuildOptionList($currval, "FieldOptions: $field_name" );
361 $field->OptionList = $option_list;
363 return "<select class=\"entry\" name=\"$field_name\"$attributes>$option_list</select>";
364 case "checkbox":
365 switch ( $field_value ) {
366 case 'f':
367 case 'off':
368 case 'false':
369 case '':
370 case '0':
371 $checked = "";
372 break;
374 default:
375 $checked = " CHECKED";
377 return "<input type=\"hidden\" value=\"off\" name=\"$field_name\"><input class=\"entry\" type=\"checkbox\" value=\"on\" name=\"$field_name\"$checked$attributes>";
378 case "input":
379 $size = (isset($part3) ? $part3 : 6);
380 return "<input class=\"entry\" value=\"".htmlspecialchars($field_value)."\" name=\"$field_name\" size=\"$size\"$attributes>";
381 case "file":
382 $size = (isset($part3) ? $part3 : 30);
383 return "<input type=\"file\" class=\"entry\" value=\"".htmlspecialchars($field_value)."\" name=\"$field_name\" size=\"$size\"$attributes>";
384 case "money":
385 $size = (isset($part3) ? $part3 : 8);
386 return "<input class=\"money\" value=\"".htmlspecialchars(sprintf("%0.2lf",$field_value))."\" name=\"$field_name\" size=\"$size\"$attributes>";
387 case "date":
388 $size = (isset($part3) ? $part3 : 10);
389 return "<input class=\"date\" value=\"".htmlspecialchars($field_value)."\" name=\"$field_name\" size=\"$size\"$attributes>";
390 case "textarea":
391 list( $cols, $rows ) = split( 'x', $part3);
392 return "<textarea class=\"entry\" name=\"$field_name\" rows=\"$rows\" cols=\"$cols\"$attributes>".htmlspecialchars($field_value)."</textarea>";
393 case "hidden":
394 return sprintf( "<input type=\"hidden\" value=\"%s\" name=\"$field_name\">", htmlspecialchars($field_value) );
395 case "password":
396 return sprintf( "<input type=\"password\" value=\"%s\" name=\"$field_name\" size=\"10\">", htmlspecialchars($part3) );
397 case "encval":
398 return htmlspecialchars($field_value);
399 case "enc":
400 return htmlspecialchars($this->Record->{$field_name});
401 case "submit":
402 $action = ( $this->RecordAvailable ? 'update' : 'insert' );
403 return sprintf('<input type="hidden" name="_editor_action[%s]" value="%s"><input type="submit" class="submit" name="%s" value="%s">',
404 $this->Id, $action, $this->SubmitName, $value_field_name );
405 default:
406 return str_replace( "\n", "<br />", $field_value );
411 * Render the templated component. The heavy lifting is done by the callback...
413 function Render( $title_tag = null ) {
414 dbg_error_log("classEditor", "Rendering editor $this->Title" );
415 if ( $this->Template == "" ) $this->DefaultTemplate();
417 $html = sprintf('<div class="editor" id="%s">', $this->Id);
418 if ( isset($this->Title) && $this->Title != "" ) {
419 if ( !isset($title_tag) ) $title_tag = 'h1';
420 $html = "<$title_tag>$this->Title</$title_tag>\n";
423 // Stuff like "##fieldname.part## gets converted to the appropriate value
424 $replaced = preg_replace_callback("/##([^#.]+)(\.([^#.]+))?(\.([^#.]+))?##/", array(&$this, "ReplaceEditorPart"), $this->Template );
425 $html .= $replaced;
427 $html .= '</div>';
428 return $html;
432 * Write the record
433 * @param boolean $is_update Tell the write whether it's an update or insert. Hopefully it
434 * should be able to figure it out though.
436 function Write( $is_update = null ) {
437 global $c, $component;
439 dbg_error_log("classEditor", "DBG: Writing editor $this->Title");
441 if ( !isset($is_update) ) {
442 if ( isset( $_POST['_editor_action']) && isset( $_POST['_editor_action'][$this->Id]) ) {
443 $is_update = ( $_POST['_editor_action'][$this->Id] == 'update' );
445 else {
446 /** @todo Our old approach will not work for translation. We need to have a hidden field
447 * containing the submittype. Probably we should add placeholders like ##form##, ##script## etc.
448 * which the editor can use for internal purposes.
450 // Then we dvine the action by looking at the submit button value...
451 $is_update = preg_match( '/(save|update|apply)/i', $_POST[$this->SubmitName] );
452 dbg_error_log("ERROR", $_SERVER['REQUEST_URI']. " is using a deprecated method for controlling insert/update" );
455 $this->Action = ( $is_update ? "update" : "create" );
456 $qry = new PgQuery( sql_from_post( $this->Action, $this->BaseTable, "WHERE ".$this->Where ) );
457 if ( !$qry->Exec("Editor::Write") ) {
458 $c->messages[] = "ERROR: $qry->errorstring";
459 return 0;
461 if ( $this->Action == "create" && isset($this->NewWhere) ) {
462 $this->GetRecord($this->NewWhere);
464 else {
465 $this->GetRecord($this->Where);
467 return $this->Record;