3 * Class for editing a record using a templated form.
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");
16 * A class for the fields in the editor
28 function __construct( $field, $sql="", $lookup_sql="" ) {
30 $this->Field
= $field;
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 ) {
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"' : '' );
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>";
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() {
86 if ( count($this->Attributes
) == 0 ) return $attributes;
87 foreach( $this->Attributes
AS $k => $v ) {
88 $attributes .= " $k=\"" . str_replace('"', '\\"', $v) . '"';
97 * The class for the Editor form in full
114 var $RecordAvailable;
119 function __construct( $title = "", $fields = null ) {
120 global $c, $session, $form_id_increment;
121 $this->Title
= $title;
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 ) {
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 ) {
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 if (is_object($this->Fields
[$field])) {
160 $this->Fields
[$field]->SetLookup( $lookup_sql );
164 function Value( $value_field_name ) {
165 if ( !isset($this->Record
->{$value_field_name}) ) return null;
166 return $this->Record
->{$value_field_name};
169 function Assign( $value_field_name, $new_value ) {
170 $this->Record
->{$value_field_name} = $new_value;
173 function Id( $id = null ) {
174 if ( isset($id) ) $this->Id
= preg_replace( '#[^a-z0-9_+-]#', '', $id);
178 function SetOptionList( $field, $options, $current = null, $parameters = null) {
179 $this->Fields
[$field]->SetOptionList( $options, $current, $parameters );
182 function AddAttribute( $field, $k, $v ) {
183 $this->Fields
[$field]->AddAttribute($k,$v);
187 function SetBaseTable( $base_table ) {
188 $this->BaseTable
= $base_table;
191 function SetJoins( $join_list ) {
192 $this->Joins
= $join_list;
197 * Accessor for the Title for the browse, which could set the title also.
199 * @param string $new_title The new title for the browser
200 * @return string The current title for the browser
202 function Title( $new_title = null ) {
203 if ( isset($new_title) ) $this->Title
= $new_title;
208 function SetSubmitName( $new_submit ) {
209 $this->SubmitName
= $new_submit;
212 function IsSubmit() {
213 return isset($_POST[$this->SubmitName
]);
216 function IsUpdate() {
217 $is_update = $this->Available();
218 if ( isset( $_POST['_editor_action']) && isset( $_POST['_editor_action'][$this->Id
]) ) {
219 $is_update = ( $_POST['_editor_action'][$this->Id
] == 'update' );
220 dbg_error_log("ERROR", "Checking update: %s => %d", $_POST['_editor_action'][$this->Id
], $is_update );
225 function IsCreate() {
226 return ! $this->IsUpdate();
229 function SetWhere( $where_clause ) {
230 $this->Where
= $where_clause;
233 function WhereNewRecord( $where_clause ) {
234 $this->NewWhere
= $where_clause;
237 function MoreWhere( $operator, $more_where ) {
238 if ( $this->Where
== "" ) {
239 $this->Where
= $more_where;
242 $this->Where
= "$this->Where $operator $more_where";
245 function AndWhere( $more_where ) {
246 $this->MoreWhere("AND",$more_where);
249 function OrWhere( $more_where ) {
250 $this->MoreWhere("OR",$more_where);
253 function SetTemplate( $template ) {
254 $this->Template
= $template;
257 function Layout( $template ) {
258 if ( strstr( $template, '##form##' ) === false && stristr( $template, '<form' ) === false ) $template = '##form##' . $template;
259 if ( stristr( $template, '</form' ) === false ) $template .= '</form>';
260 $this->Template
= $template;
263 function Available( ) {
264 return $this->RecordAvailable
;
267 function SetRecord( $row ) {
268 $this->Record
= $row;
269 $this->RecordAvailable
= is_object($this->Record
);
270 return $this->Record
;
274 * Set some particular values to the ones from the array.
276 * @param array $values An array of fieldname / value pairs
278 function Initialise( $values ) {
279 $this->RecordAvailable
= false;
280 foreach( $values AS $fname => $value ) {
281 $this->Record
->{$fname} = $value;
287 * This will assign $_POST values to the internal Values object for each
288 * field that exists in the Fields array.
290 function PostToValues( $prefix = '' ) {
291 foreach ( $this->Fields
AS $fname => $fld ) {
292 @dbg_error_log
( 'classEditor', ":PostToValues: %s => %s", $fname, $_POST["$prefix$fname"] );
293 if ( isset($_POST[$prefix.$fname]) ) {
294 $this->Record
->{$fname} = $_POST[$prefix.$fname];
295 @dbg_error_log
( 'classEditor', ":PostToValues: %s => %s", $fname, $_POST["$prefix$fname"] );
300 function GetRecord( $where = "" ) {
303 foreach( $this->Fields
AS $k => $column ) {
304 if ( $target_fields != "" ) $target_fields .= ", ";
305 $target_fields .= $column->GetTarget();
307 if ( $where == "" ) $where = $this->Where
;
308 $sql = sprintf( "SELECT %s FROM %s %s WHERE %s %s %s",
309 $target_fields, $this->BaseTable
, $this->Joins
, $where, $this->Order
, $this->Limit
);
310 $this->Query
= new PgQuery( $sql );
311 $session->Log("DBG: EditorGetQry: %s", $sql );
312 if ( $this->Query
->Exec("Browse:$this->Title:DoQuery") ) {
313 $this->Record
= $this->Query
->Fetch();
314 $this->RecordAvailable
= is_object($this->Record
);
316 if ( !$this->RecordAvailable
) {
317 $this->Record
= (object) array();
319 return $this->Record
;
324 * Replace parts into the form template.
325 * @param array $matches The matches found which preg_replace_callback is calling us for.
326 * @return string What we want to replace this match with.
328 function ReplaceEditorPart($matches)
332 // $matches[0] is the complete match
333 switch( $matches[0] ) {
334 case "##form##": /** @todo It might be nice to construct a form ID */
335 return sprintf('<form method="POST" enctype="multipart/form-data" class="editor" id="%s">', $this->Id
);
337 $action = ( $this->RecordAvailable ?
'update' : 'insert' );
338 $submittype = ($this->RecordAvailable ?
translate('Apply Changes') : translate('Create'));
339 return sprintf('<input type="hidden" name="_editor_action[%s]" value="%s"><input type="submit" class="submit" name="%s" value="%s">',
340 $this->Id
, $action, $this->SubmitName
, $submittype );
343 // $matches[1] the match for the first subpattern
344 // enclosed in '(...)' and so on
345 $field_name = $matches[1];
346 $what_part = $matches[3];
347 $part3 = (isset($matches[5]) ?
$matches[5] : null);
349 $value_field_name = $field_name;
350 if ( substr($field_name,0,4) == 'xxxx' ) {
351 // Sometimes we will prepend 'xxxx' to the field name so that the field
352 // name differs from the column name in the database. We also remove it
353 // when it's submitted.
354 $value_field_name = substr($field_name,4);
358 if ( isset($this->Fields
[$field_name]) && is_object($this->Fields
[$field_name]) ) {
359 $field = $this->Fields
[$field_name];
360 $attributes = $field->RenderAttributes();
362 $field_value = (isset($this->Record
->{$value_field_name}) ?
$this->Record
->{$value_field_name} : null);
364 switch( $what_part ) {
367 if ( ! isset($currval) && isset($field_value) )
368 $currval = $field_value;
369 if ( isset($field->OptionList
) && $field->OptionList
!= "" ) {
370 $option_list = $field->OptionList
;
373 $session->Log("DBG: Current=%s, OptionQuery: %s", $currval, $field->LookupSql
);
374 $opt_qry = new PgQuery( $field->LookupSql
);
375 $option_list = $opt_qry->BuildOptionList($currval, "FieldOptions: $field_name" );
376 $field->OptionList
= $option_list;
381 if ( ! isset($currval) && isset($field_value) )
382 $currval = $field_value;
383 if ( isset($field->OptionList
) && $field->OptionList
!= "" ) {
384 $option_list = $field->OptionList
;
387 $session->Log("DBG: Current=%s, OptionQuery: %s", $currval, $field->LookupSql
);
388 $opt_qry = new PgQuery( $field->LookupSql
);
389 $option_list = $opt_qry->BuildOptionList($currval, "FieldOptions: $field_name" );
390 $field->OptionList
= $option_list;
392 return "<select class=\"entry\" name=\"$field_name\"$attributes>$option_list</select>";
394 switch ( $field_value ) {
404 $checked = " CHECKED";
406 return "<input type=\"hidden\" value=\"off\" name=\"$field_name\"><input class=\"entry\" type=\"checkbox\" value=\"on\" name=\"$field_name\"$checked$attributes>";
408 $size = (isset($part3) ?
$part3 : 6);
409 return "<input class=\"entry\" value=\"".htmlspecialchars($field_value)."\" name=\"$field_name\" size=\"$size\"$attributes>";
411 $size = (isset($part3) ?
$part3 : 30);
412 return "<input type=\"file\" class=\"entry\" value=\"".htmlspecialchars($field_value)."\" name=\"$field_name\" size=\"$size\"$attributes>";
414 $size = (isset($part3) ?
$part3 : 8);
415 return "<input class=\"money\" value=\"".htmlspecialchars(sprintf("%0.2lf",$field_value))."\" name=\"$field_name\" size=\"$size\"$attributes>";
417 $size = (isset($part3) ?
$part3 : 10);
418 return "<input class=\"date\" value=\"".htmlspecialchars($field_value)."\" name=\"$field_name\" size=\"$size\"$attributes>";
420 list( $cols, $rows ) = split( 'x', $part3);
421 return "<textarea class=\"entry\" name=\"$field_name\" rows=\"$rows\" cols=\"$cols\"$attributes>".htmlspecialchars($field_value)."</textarea>";
423 return sprintf( "<input type=\"hidden\" value=\"%s\" name=\"$field_name\">", htmlspecialchars($field_value) );
425 return sprintf( "<input type=\"password\" value=\"%s\" name=\"$field_name\" size=\"10\">", htmlspecialchars($part3) );
428 return htmlspecialchars($field_value);
430 $action = ( $this->RecordAvailable ?
'update' : 'insert' );
431 return sprintf('<input type="hidden" name="_editor_action[%s]" value="%s"><input type="submit" class="submit" name="%s" value="%s">',
432 $this->Id
, $action, $this->SubmitName
, $value_field_name );
434 return str_replace( "\n", "<br />", $field_value );
439 * Render the templated component. The heavy lifting is done by the callback...
441 function Render( $title_tag = null ) {
442 dbg_error_log("classEditor", "Rendering editor $this->Title" );
443 if ( $this->Template
== "" ) $this->DefaultTemplate();
445 $html = sprintf('<div class="editor" id="%s">', $this->Id
);
446 if ( isset($this->Title
) && $this->Title
!= "" ) {
447 if ( !isset($title_tag) ) $title_tag = 'h1';
448 $html = "<$title_tag>$this->Title</$title_tag>\n";
451 // Stuff like "##fieldname.part## gets converted to the appropriate value
452 $replaced = preg_replace_callback("/##([^#.]+)(\.([^#.]+))?(\.([^#.]+))?##/", array(&$this, "ReplaceEditorPart"), $this->Template
);
461 * @param boolean $is_update Tell the write whether it's an update or insert. Hopefully it
462 * should be able to figure it out though.
464 function Write( $is_update = null ) {
465 global $c, $component;
467 dbg_error_log("classEditor", "DBG: Writing editor $this->Title");
469 if ( !isset($is_update) ) {
470 if ( isset( $_POST['_editor_action']) && isset( $_POST['_editor_action'][$this->Id
]) ) {
471 $is_update = ( $_POST['_editor_action'][$this->Id
] == 'update' );
474 /** @todo Our old approach will not work for translation. We need to have a hidden field
475 * containing the submittype. Probably we should add placeholders like ##form##, ##script## etc.
476 * which the editor can use for internal purposes.
478 // Then we dvine the action by looking at the submit button value...
479 $is_update = preg_match( '/(save|update|apply)/i', $_POST[$this->SubmitName
] );
480 dbg_error_log("ERROR", $_SERVER['REQUEST_URI']. " is using a deprecated method for controlling insert/update" );
483 $this->Action
= ( $is_update ?
"update" : "create" );
484 $qry = new PgQuery( sql_from_post( $this->Action
, $this->BaseTable
, "WHERE ".$this->Where
) );
485 if ( !$qry->Exec("Editor::Write") ) {
486 $c->messages
[] = "ERROR: $qry->errorstring";
489 if ( $this->Action
== "create" && isset($this->NewWhere
) ) {
490 $this->GetRecord($this->NewWhere
);
493 $this->GetRecord($this->Where
);
495 return $this->Record
;