Overview

Packages

  • awl
    • AuthPlugin
    • AwlDatabase
    • Browser
    • classEditor
    • DataEntry
    • DataUpdate
    • EMail
    • iCalendar
    • MenuSet
    • PgQuery
    • Session
    • Translation
    • User
    • Utilities
    • Validation
    • vCalendar
    • vComponent
    • XMLDocument
    • XMLElement
  • None

Classes

  • AuthPlugin
  • AwlCache
  • AwlDatabase
  • AwlDBDialect
  • AwlQuery
  • AwlUpgrader
  • Browser
  • BrowserColumn
  • DBRecord
  • Editor
  • EditorField
  • EMail
  • EntryField
  • EntryForm
  • iCalComponent
  • iCalendar
  • iCalProp
  • MenuOption
  • MenuSet
  • Multipart
  • PgQuery
  • Session
  • SinglePart
  • User
  • Validation
  • vCalendar
  • vComponent
  • vObject
  • vProperty
  • XMLDocument
  • XMLElement

Functions

  • _awl_connect_configured_database
  • _CompareMenuSequence
  • auth_external
  • auth_other_awl
  • awl_replace_sql_args
  • awl_set_locale
  • awl_version
  • BuildXMLTree
  • check_by_regex
  • check_temporary_passwords
  • clean_string
  • connect_configured_database
  • dbg_error_log
  • dbg_log_array
  • define_byte_mappings
  • deprecated
  • duration
  • fatal
  • force_utf8
  • get_fields
  • getCacheInstance
  • gzdecode
  • i18n
  • init_gettext
  • olson_from_tzstring
  • param_to_global
  • qpg
  • quoted_printable_encode
  • replace_uri_params
  • session_salted_md5
  • session_salted_sha1
  • session_simple_md5
  • session_validate_password
  • sql_from_object
  • sql_from_post
  • trace_bug
  • translate
  • uuid
  • Overview
  • Package
  • Class
  1:   2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71:  72:  73:  74:  75:  76:  77:  78:  79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89:  90:  91:  92:  93:  94:  95:  96:  97:  98:  99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 527: 528: 529: 530: 531: 532: 533: 534: 535: 536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581: 582: 583: 584: 585: 586: 587: 588: 589: 590: 591: 592: 593: 594: 595: 596: 597: 598: 599: 600: 601: 602: 603: 604: 605: 606: 607: 608: 609: 610: 611: 612: 613: 614: 615: 616: 617: 618: 619: 620: 621: 622: 623: 624: 625: 626: 627: 628: 629: 630: 631: 632: 633: 634: 635: 636: 637: 638: 639: 640: 641: 642: 643: 644: 645: 646: 647: 648: 649: 650: 651: 652: 653: 654: 655: 656: 657: 658: 659: 660: 661: 662: 663: 664: 665: 666: 667: 668: 669: 670: 671: 672: 673: 674: 675: 676: 677: 678: 679: 680: 681: 682: 683: 684: 685: 686: 687: 688: 689: 690: 691: 692: 693: 694: 695: 696: 697: 698: 699: 700: 701: 702: 703: 704: 705: 706: 707: 708: 709: 710: 711: 712: 713: 714: 715: 
<?php
/**
* Class for editing a record using a templated form.
*
* @package   awl
* @subpackage   classEditor
* @author    Andrew McMillan <andrew@mcmillan.net.nz>
* @copyright Catalyst IT Ltd, Morphoss Ltd <http://www.morphoss.com/>
* @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   awl
*/
class EditorField
{
  var $Field;
  var $Sql;
  var $Value;
  var $Attributes;
  var $LookupSql;
  var $OptionList;

  /**
   * Creates an EditorField for use in the Editor, possibly initialising the SQL for calculating it's
   * value, and lookup_sql for use in drop-down lists.
   *
   * @param unknown $field
   * @param string $sql
   * @param string $lookup_sql
   */
  function __construct( $field, $sql="", $lookup_sql="" ) {
    global $session;
    $this->Field      = $field;
    $this->Sql        = $sql;
    $this->LookupSql  = $lookup_sql;
    $this->Attributes = array();
  }

  function Set($value) {
    $this->Value = $value;
  }

  /**
   * Set the SQL used for this field, if it is more than just a field name.
   * @param unknown $sql
   */
  function SetSql( $sql ) {
    $this->Sql  = $sql;
  }

  /**
   * Set the lookup SQL to use to populate a SELECT for this field.
   * @param string $lookup_sql
   */
  function SetLookup( $lookup_sql ) {
    $this->LookupSql  = $lookup_sql;
  }

  /**
   * Set the SELECT values explicitly, if they are not available in SQL.
   *
   * For example:
   *
   *   SetOptionList(array('M' => 'Male', 'F' => 'Female', 'O' => 'Other'), 'F', array('maxwidth' => 6, 'translate' => true));
   *
   * This would present Male/Female/Other drop-down, when in another language the values
   * would be translated (if available), e.g. in German as Männlich/Weiblich/Andere, except
   * that in this case Männli/Weibli/Andere, since the values would be truncated to maxwidth.
   *
   * @param array $options An array of key => value pairs
   * @param string $current The currently selected key
   * @param string $parameters An array of parameters (maxwidth & translate are the only valid parameters)
   */
  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 .= "<option value=\"".htmlspecialchars($k)."\"$selected>".htmlspecialchars($v)."</option>";
      }
    }
    else {
      $this->OptionList = $options;
    }
  }

  function GetTarget() {
    if ( $this->Sql == "" ) return $this->Field;
    return "$this->Sql AS $this->Field";
  }

  /**
   * Add some kind of attribute to this field, such as a 'class' => 'fancyinputthingy'
   *
   * @param string $k The attribute name
   * @param string $v The attribute value
   */
  function AddAttribute( $k, $v ) {
    $this->Attributes[$k] = $v;
  }

  /**
   * Render a LABEL around something.  In particular it is useful to render a label around checkbox fields to include their labels and make them clickable.
   *
   * The label value itself must be in the '_label' attribute, and the field must also have an 'id' attribute.
   *
   * @param string $wrapme The rendered field to be wrapped
   * @return string
   */
  function RenderLabel( $wrapme ) {
    if ( !isset($this->Attributes['_label']) || !isset($this->Attributes['id'])) return $wrapme;
    $class = (isset($this->Attributes['class']) ? $this->Attributes['class'] : 'entry');
    $title = (isset($this->Attributes['title']) ? ' title="'.str_replace('"', '&#39;', $this->Attributes['title']) . '"' : '');
    return( sprintf( '<label for="%s" class="%s"%s>%s %s</label>',
             $this->Attributes['id'], $class, $title, $wrapme, $this->Attributes['_label']) );
  }

  /**
   * Render the array of attributes for inclusion in the input tag.
   * @return string
   */
  function RenderAttributes() {
    $attributes = "";
    if ( count($this->Attributes) == 0 ) return $attributes;
    foreach( $this->Attributes AS $k => $v ) {
      if ( $k == '_label' ) continue;
      $attributes .= " $k=\"" . str_replace('"', '&#39;', $v) . '"';
    }
    return $attributes;
  }



}



/**
* The class for the Editor form in full
* @package awl
*/
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;

  /**
   * Constructs an editor widget, with a title and fields.
   *
   * The second parameter maybe passed as a string, to be interpreted as the name of a table, from
   * which all fields will be included, or as an array of specific fields, in which case you should
   * make sure to call SetBaseTable('tablename') so the editor knows where to find those fields!
   *
   * @param string $title
   * @param array or string $fields See above
   */
  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( 'editor', 'DBG: New editor called %s', $title);
  }

  /**
   * Creates a new field in the Editor, possibly initialising the SQL for calculating it's
   * value, and lookup_sql for use in drop-down lists.
   *
   * @param string $field The name for the field.
   * @param string $sql The SQL for the target list. Think: "$sql AS $field"
   * @param string $lookup_sql The SQL for looking up a list of possible stored values and displayed values.
   */
  function &AddField( $field, $sql="", $lookup_sql="" ) {
    $this->Fields[$field] = new EditorField( $field, $sql, $lookup_sql );
    $this->OrderedFields[] = $field;
    return $this->Fields[$field];
  }

  /**
   * Set the SQL for this field for the target list.  Think: "$sql AS $field"
   * @param string $field
   * @param string $sql
   */
  function SetSql( $field, $sql ) {
    $this->Fields[$field]->SetSql( $sql );
  }

  /**
   * Set the SQL for looking up a list of possible stored values and displayed values.
   * @param string $field
   * @param string $lookup_sql
   */
  function SetLookup( $field, $lookup_sql ) {
    if (is_object($this->Fields[$field])) {
      $this->Fields[$field]->SetLookup( $lookup_sql );
    }
  }

  /**
   * Gets the value of a field in the record currently assigned to this editor.
   * @param string $value_field_name
   */
  function Value( $value_field_name ) {
    if ( !isset($this->Record->{$value_field_name}) ) return null;
    return $this->Record->{$value_field_name};
  }

  /**
   * Assigns the value of a field in the record currently associated with this editor.
   * @param string $value_field_name
   * @param string $new_value
   */
  function Assign( $value_field_name, $new_value ) {
    if ( !isset($this->Record) ) $this->Record = (object) array();
    $this->Record->{$value_field_name} = $new_value;
  }

  /**
   * Sets or returns the form ID used for differentiating this form from others in the page.
   * @param string $id
   */
  function Id( $id = null ) {
    if ( isset($id) ) $this->Id = preg_replace( '#[^a-z0-9_+-]#', '', $id);
    return $this->Id;
  }

  /**
   * Set the explicit options & parameters for a list of stored/displayed values.  See the
   * description under EditorField::SetOptionList() for full details.
   *
   * @param string $field
   * @param array $options A key => value array of valid store => display values.
   * @param string $current The key of the current row
   * @param string $parameters Set maxwidth & whether displayed values are translated.
   */
  function SetOptionList( $field, $options, $current = null, $parameters = null) {
    $this->Fields[$field]->SetOptionList( $options, $current, $parameters );
  }

  /**
   * Add an attribute to this field.
   * @param unknown $field
   * @param unknown $k
   * @param unknown $v
   */
  function AddAttribute( $field, $k, $v ) {
    $this->Fields[$field]->AddAttribute($k,$v);

  }

  /**
   * Set the base table for the row query.
   * @param unknown $base_table
   */
  function SetBaseTable( $base_table ) {
    $this->BaseTable = $base_table;
  }

  /**
   * Set any joins
   * @param unknown $join_list
   */
  function SetJoins( $join_list ) {
    $this->Joins = $join_list;
  }


  /**
  * Accessor for the Title for the editor, 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;
  }


  /**
   * Set the name of the SUBMIT button
   * @param unknown $new_submit
   */
  function SetSubmitName( $new_submit ) {
    $this->SubmitName = $new_submit;
  }

  function IsSubmit() {
    return isset($_POST[$this->SubmitName]);
  }

  /**
   * Magically knows whether you are in the processing the result of an update or a create.
   * @return boolean
   */
  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( 'editor', 'Checking update: %s => %d', $_POST['_editor_action'][$this->Id], $is_update );
    }
    return $is_update;
  }

  /**
   * The opposite of IsUpdate.  Really.
   * @return boolean
   */
  function IsCreate() {
    return ! $this->IsUpdate();
  }

  /**
   * Set the row selection criteria
   * @param unknown $where_clause
   */
  function SetWhere( $where_clause ) {
    $this->Where = $where_clause;
  }

  /**
   * Set the criteria used to find the new row after it got created.
   * @param unknown $where_clause
   */
  function WhereNewRecord( $where_clause ) {
    $this->NewWhere = $where_clause;
  }

  /**
   * Append more stuff to the WHERE clause
   * @param unknown $operator
   * @param unknown $more_where
   */
  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);
  }

  /**
   * Set this to be the form display template.  It's better to use Layout($template) in general.
   *
   * @deprecated
   * @param string $template
   */
  function SetTemplate( $template ) {
    deprecated('Editor::SetTemplate');
    $this->Template = $template;
  }

  /**
   * Like SetTemplate($template) except it surrounds the template with a ##form## ... </form> if
   * there is not a form already in the template.
   *
   * @param string $template
   */
  function Layout( $template ) {
    if ( strstr( $template, '##form##' ) === false && stristr( $template, '<form' ) === false )
      $template = '##form##' . $template;
    if ( stristr( $template, '</form' ) === false ) $template .= '</form>';
    $this->Template = $template;
  }

  /**
   * Returns 'true' if we have read a row from the database (or set one through SetRecord()), 'false' otherwise.
   *
   * @return boolean
   */
  function Available( ) {
    return $this->RecordAvailable;
  }

  /**
   * Set a database row to load the field values from.
   *
   * @param object $row
   * @return object The row that was passed in.
   */
  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;
    if ( !isset($this->Record) ) $this->Record = (object) array();
    foreach( $values AS $fname => $value ) {
      $this->Record->{$fname} = $value;
    }
  }


  /**
  * This will assign $_POST values to the internal Values object for each
  * field that exists in the Fields array.
  */
  function PostToValues( $prefix = '' ) {
    foreach ( $this->Fields AS $fname => $fld ) {
      @dbg_error_log( 'editor', ":PostToValues: %s => %s", $fname, $_POST["$prefix$fname"] );
      if ( isset($_POST[$prefix.$fname]) ) {
        $this->Record->{$fname} = $_POST[$prefix.$fname];
        @dbg_error_log( 'editor', ":PostToValues: %s => %s", $fname, $_POST["$prefix$fname"] );
      }
    }
  }

  /**
   * Read the record from the database, optionally overriding the WHERE clause.
   *
   * @param string $where (optional) An SQL WHERE clause to override any previous SetWhere call.
   * @return object The row that was read from the database.
   */
  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 AwlQuery( $sql );
    @dbg_error_log( 'editor', "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.  Parts that are replaceable are listed below:
  *   ##form##        A <form ...> tag.  You should close this with </form> or use Layout($template) which will take care of it for you.
  *   ##submit##      A <input type="submit" ...> tag for the form.
  *   ##f.options##   A list of options explicitly specified
  *   ##f.select##    A select list from the lookup SQL specified
  *   ##f.checkbox##  A checkbox, perhaps with a "_label" attribute
  *   ##f.input##     A normal input field.
  *   ##f.file##      A file upload field.
  *   ##f.money##     A money input field.
  *   ##f.date##      A date input field.
  *   ##f.textarea##  A textarea
  *   ##f.hidden##    A hidden input field
  *   ##f.password##  An input field for entering passwords without them being echoed to the screen
  *   ##f.enc##       Just print the value with special chars escaped for use in URLs.
  *   ##f.submit##    An <input type="submit" where you specify the field name.
  *
  * Most of these begin with "f", which should be replaced by the name of the field.  Many also take an option
  * after the name as well, so (for example) you can force the current value in ##options## or ##select## by
  * setting ##field.select.current##.  The input, file, money & date all accept the third parameter as a size
  * value, so ##fieldname.date.14## would be a 14-character-wide date field. Similarly a textarea allows for
  * a COLSxROWS value, so ##myfield.textarea.80x5## would be an 80-column textarea, five rows high.
  *
  * For ##fieldname.password.fakevalue## you can set the 'fake' value used to populate the password field so
  * that you can check for this on submit to be able to tell whether the password field has been edited.
  *
  * Other attributes are added to the <input ...> tag based on any SetAttributes() that may have been applied.
  *
  * @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('<form method="POST" enctype="multipart/form-data" class="editor" id="form_%s">', $this->Id);
      case "##submit##":
        $action =  ( $this->RecordAvailable ? 'update' : 'insert' );
        $submittype = ($this->RecordAvailable ? translate('Apply Changes') : translate('Create'));
        return sprintf('<input type="hidden" name="_editor_action[%s]" value="%s"><input type="submit" class="submit" name="%s" value="%s">',
                                                              $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 = (isset($matches[3]) ? $matches[3] : null);
    $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 {
          @dbg_error_log( 'editor', "DBG: Current=%s, OptionQuery: %s", $currval, $field->LookupSql );
          $opt_qry = new AwlQuery( $field->LookupSql );
          $option_list = EntryField::BuildOptionList($opt_qry, $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 {
          @dbg_error_log( 'editor', 'DBG: Current=%s, OptionQuery: %s', $currval, $field->LookupSql );
          $opt_qry = new AwlQuery( $field->LookupSql );
          $option_list = EntryField::BuildOptionList($opt_qry, $currval, 'FieldOptions: '.$field_name );
          $field->OptionList = $option_list;
        }
        return '<select class="entry" name="'.$field_name.'"'.$attributes.'>'.$option_list.'</select>';
      case "checkbox":
        if ( !isset($field) ) {
          @dbg_error_log("ERROR","Field '$field_name' is not defined.");
          return "<p>Error: '$field_name' is not defined.</p>";
        }
        if ( $field_value === true ) {
          $checked = ' CHECKED';
        }
        else {
          switch ( $field_value ) {
            case 'f':
            case 'off':
            case 'false':
            case '':
            case '0':
              $checked = "";
              break;

            default:
              $checked = ' CHECKED';
          }
        }
        return $field->RenderLabel('<input type="hidden" value="off" name="'.$field_name.'"><input class="entry" type="checkbox" value="on" name="'.$field_name.'"'.$checked.$attributes.'>' );
      case "input":
        $size = (isset($part3) ? $part3 : 6);
        return "<input class=\"entry\" value=\"".htmlspecialchars($field_value)."\" name=\"$field_name\" size=\"$size\"$attributes>";
      case "file":
        $size = (isset($part3) ? $part3 : 30);
        return "<input type=\"file\" class=\"entry\" value=\"".htmlspecialchars($field_value)."\" name=\"$field_name\" size=\"$size\"$attributes>";
      case "money":
        $size = (isset($part3) ? $part3 : 8);
        return "<input class=\"money\" value=\"".htmlspecialchars(sprintf("%0.2lf",$field_value))."\" name=\"$field_name\" size=\"$size\"$attributes>";
      case "date":
        $size = (isset($part3) ? $part3 : 10);
        return "<input class=\"date\" value=\"".htmlspecialchars($field_value)."\" name=\"$field_name\" size=\"$size\"$attributes>";
      case "textarea":
        list( $cols, $rows ) = explode( 'x', $part3);
        return "<textarea class=\"entry\" name=\"$field_name\" rows=\"$rows\" cols=\"$cols\"$attributes>".htmlspecialchars($field_value)."</textarea>";
      case "hidden":
        return sprintf( "<input type=\"hidden\" value=\"%s\" name=\"$field_name\">", htmlspecialchars($field_value) );
      case "password":
        return sprintf( "<input type=\"password\" value=\"%s\" name=\"$field_name\" size=\"10\">", htmlspecialchars($part3) );
      case "encval":
      case "enc":
        return htmlspecialchars($field_value);
      case "submit":
        $action =  ( $this->RecordAvailable ? 'update' : 'insert' );
        return sprintf('<input type="hidden" name="_editor_action[%s]" value="%s"><input type="submit" class="submit" name="%s" value="%s">',
                                                              $this->Id, $action, $this->SubmitName, $value_field_name );
      default:
        return str_replace( "\n", "<br />", $field_value );
    }
  }

  /**
  * Render the templated component.  The heavy lifting is done by the callback...
  */
  function Render( $title_tag = null ) {
    @dbg_error_log( 'editor', "classEditor", "Rendering editor $this->Title" );
    if ( $this->Template == "" ) $this->DefaultTemplate();

    $html = sprintf('<div class="editor" id="%s">', $this->Id);
    if ( isset($this->Title) && $this->Title != "" ) {
      if ( !isset($title_tag) ) $title_tag = 'h1';
      $html = "<$title_tag>$this->Title</$title_tag>\n";
    }

    // Stuff like "##fieldname.part## gets converted to the appropriate value
    $replaced = preg_replace_callback("/##([^#.]+)(\.([^#.]+))?(\.([^#.]+))?##/", array(&$this, "ReplaceEditorPart"), $this->Template );
    $html .= $replaced;

    $html .= '</div>';
    return $html;
  }

  /**
  * Write the record.  You might want to consider calling Editor::WhereNewRecord() before this if it might be creating a new record.
  * @param boolean $is_update Explicitly tell the write whether it's an update or insert.  Generally it should be able to figure it out though.
  */
  function Write( $is_update = null ) {
    global $c, $component;

    @dbg_error_log( 'editor', 'DBG: Writing editor %s', $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('WARN', $_SERVER['REQUEST_URI']. " is using a deprecated method for controlling insert/update" );
      }
    }
    $this->Action = ( $is_update ? "update" : "create" );
    $qry = new AwlQuery( 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;
  }
}

API documentation generated by ApiGen