Current File : //opt/RZphp74/includes/DB/DataObject/FormBuilder.php |
<?php
/**
* This package helps with type and foreign key aware rapid development of forms
* as well as many options to allow the advanced features to be used in a production
* environment.
*
* All the settings for FormBuilder must be in a section [DB_DataObject_FormBuilder]
* within the DataObject.ini file (or however you've named it).
* If you stuck to the DB_DataObject example in the doc, you'll read in your
* config like this:
* <code>
* $config = parse_ini_file('DataObject.ini', true);
* foreach ($config as $class => $values) {
* $options = &PEAR::getStaticProperty($class, 'options');
* $options = $values;
* }
* </code>
* Now you're ready to go!
*
* You can also set any option through your DB_DataObject derived classes by
* prepending 'fb_' to the option name. Ex: 'fb_fieldLabels'. This is the
* preferred way of setting DataObject-specific options.
*
* You may also set all options manually by setting them in the DO ir FB objects.
*
* You may also set the options in an FB derived class, but this isn't as well
* supported.
*
* In addition, there are special methods you can define in your DataObject classes for even more control.
* <ul>
* <li>preGenerateForm(&$formBuilder):
* This method will be called before the form is generated. Use this to change
* property values or options in your DataObject. This is the normal plave to
* set up fb_preDefElements. Note: the $formBuilder object passed in has not
* yet copied the options from the DataObject into it. If you plan to use the
* functions in FB in this method, call populateOptions() on it first.
* </li>
* <li>postGenerateForm(&$form, &$formBuilder):
* This method will be called after the form is generated. The form is passed in by reference so you can
* alter it. Use this method to add, remove, or alter elements in the form or the form itself.
* </li>
* <li>preProcessForm(&$values, &$formBuilder):
* This method is called just before FormBuilder processes the submitted form data. The values are sent
* by reference in the first parameter as an associative array. The key is the element name and the value
* the submitted value. You can alter the values as you see fit (md5 on passwords, for example).
* </li>
* <li>postProcessForm(&$values, &$formBuilder):
* This method is called just after FormBuilder processed the submitted form data. The values are again
* sent by reference. This method could be used to inform the user of changes, alter the DataObject, etc.
* </li>
* <li>getForm($action, $target, $formName, $method, &$formBuilder):
* If this function exists, it will be used instead of FormBuilder's internal form generation routines
* Use this only if you want to create the entire form on your own.
* </li>
* </ul>
*
* Note for PHP5-users: These properties have to be public! In general, you can
* override all settings from the .ini file by setting similarly-named properties
* in your DataObject classes.
*
* <b>Most basic usage:</b>
* <code>
* $do =& new MyDataObject();
* // Insert "$do->get($some_id);" here to edit an existing object instead of making a new one
* $fg =& DB_DataObject_FormBuilder::create($do);
* $form =& $fg->getForm();
* if ($form->validate()) {
* $form->process(array(&$fg,'processForm'), false);
* $form->freeze();
* }
* $form->display();
* </code>
*
* For more information on how to use the DB_DataObject or HTML_QuickForm packages
* themselves, please see the excellent documentation on http://pear.php.net/.
*
*
* PHP versions 4 and 5
*
* @category DB
* @package DB_DataObject_FormBuilder
* @author Markus Wolff <mw21st@php.net>
* @author Justin Patrin <papercrane@reversefold.com>
* @copyright 1997-2006 The PHP Group
* @license http://www.gnu.org/licenses/lgpl.txt LGPL 2.1
* @version $Id$
* @link http://pear.php.net/package/DB_DataObject_FormBuilder
* @see DB_DataObject, HTML_QuickForm
*/
// Import requirements
require_once ('DB/DataObject.php');
// Constants used for forceQueryType()
define ('DB_DATAOBJECT_FORMBUILDER_QUERY_AUTODETECT', 0);
define ('DB_DATAOBJECT_FORMBUILDER_QUERY_FORCEINSERT', 1);
define ('DB_DATAOBJECT_FORMBUILDER_QUERY_FORCEUPDATE', 2);
define ('DB_DATAOBJECT_FORMBUILDER_QUERY_FORCENOACTION', 3);
// Constants used for cross/triple links
define ('DB_DATAOBJECT_FORMBUILDER_CROSSLINK', 1048576);
define ('DB_DATAOBJECT_FORMBUILDER_TRIPLELINK', 2097152);
define ('DB_DATAOBJECT_FORMBUILDER_ENUM', 4194304);
define ('DB_DATAOBJECT_FORMBUILDER_REVERSELINK', 8388608);
define ('DB_DATAOBJECT_FORMBUILDER_GROUP', 16777216);
// Error code constants
define ('DB_DATAOBJECT_FORMBUILDER_ERROR_UNKNOWNDRIVER', 4711);
define ('DB_DATAOBJECT_FORMBUILDER_ERROR_NODATAOBJECT', 4712);
/**
* This is the main class for FormBuilder. It does most of the real work.
*
* This class deals with all output agnostic portions of work, although
* there are still some bits of HTML generation and such to be cleaned out.
*
* You cannot use this class on its own. You must use a driver. The correct
* way to instantiate a driver is:
* <code>
* $do = DB_DataObject::factory('someTable');
* $options = array('someOption' +> 'someValue');
* $formBuilder =& DB_DataObject_FormBuilder::create($do, $options, 'DriverName');
* </code>
* The easiest form, if you need to set no options via the create() and you wish
* to use HTML_QuickForm is:
* <code>
* $do = DB_DataObject::factory('someTable');
* $formBuilder =& DB_DataObject_FormBuilder::create($do);
* </code>
*
* @see DB_DataObject_FormBuilder_QuickForm
*/
class DB_DataObject_FormBuilder
{
//PROTECTED vars
/**
* If you want to use the generator on an existing form object, pass it
* to useForm().
*
* @access protected
* @see DB_DataObject_Formbuilder()
*/
var $_form = false;
/**
* Contains the last validation errors, if validation checking is enabled.
*
* @access protected
*/
var $_validationErrors = false;
/**
* Used to determine which action to perform with the submitted data in processForm()
*
* @access protected
*/
var $_queryType = DB_DATAOBJECT_FORMBUILDER_QUERY_AUTODETECT;
/**
* Is set to true if excludeFromAutoRules is set to __ALL__ (or includes __ALL__)
*/
var $_excludeAllFromAutoRules = false;
//PUBLIC vars
/**
* Add a header to the form - if set to true, the form will
* have a header element as the first element in the form.
*
* @access public
* @see formHeaderText
*/
var $addFormHeader = true;
/**
* Text for the form header. If not set, the name of the database
* table this form represents will be used.
*
* @access public
* @see addFormHeader
*/
var $formHeaderText = null;
/**
* Text that is displayed as an error message if a validation rule
* is violated by the user's input. Use %s to insert the field name.
*
* @access public
* @see requiredRuleMessage
*/
var $ruleViolationMessage = '%s: The value you have entered is not valid.';
/**
* Text that is displayed as an error message if a required field is
* left empty. Use %s to insert the field name.
*
* @access public
* @see ruleViolationMessage
*/
var $requiredRuleMessage = 'The field %s is required.';
/**
* If set to TRUE, the current DataObject's validate method is being called
* before the form data is processed. If errors occur, no insert/update operation
* will be made on the database. Use getValidationErrors() to retrieve the reasons
* for a failure.
* Defaults to FALSE.
*
* @access public
*/
var $validateOnProcess = false;
/**
* The language used in date fields. See documentation of HTML_Quickform's
* date element for more information.
*
* @see HTML_QuickForm_date
*/
var $dateFieldLanguage = 'en';
/**
* Callback method to convert a date from the format it is stored
* in the database to the format used by the QuickForm element that
* handles date values. Must have a format usable with call_user_func().
*/
var $dateFromDatabaseCallback = array('DB_DataObject_FormBuilder','_date2array');
/**
* Callback method to convert a date from the format used by the QuickForm
* element that handles date values to the format the database can store it in.
* Must have a format usable with call_user_func().
*/
var $dateToDatabaseCallback = array('DB_DataObject_FormBuilder','_array2date');
/**
* A format string that represents the display settings for QuickForm date elements.
* Example: "d-m-Y". See QuickForm documentation for details on format strings.
* Legal letters to use in the format string that work with FormBuilder are:
* d,m,M,y,Y
*/
var $dateElementFormat = 'd-m-Y';
/**
* A format string that represents the display settings for QuickForm time elements.
* Example: "H:i:s". See QuickForm documentation for details on format strings.
* Legal letters to use in the format string that work with FormBuilder are:
* H,i,s
*/
var $timeElementFormat = 'H:i:s';
/**
* A format string that represents the display settings for QuickForm datetime elements.
* Example: "d-m-Y H:i:s". See QuickForm documentation for details on format strings.
* Legal letters to use in the format string that work with FormBuilder are:
* d,m,M,y,Y,H,i,s
*/
var $dateTimeElementFormat = 'd-m-Y H:i:s';
/**
* This is for the future support of string date formats other than ISO, but
* currently, that's the only supported one. Set to 1 for ISO, other values
* may be available later on.
*/
var $dbDateFormat = 1;
/**
* Callback to get extra options for date elements. Defaults to 'dateOptions' in the DO.
*/
var $dateOptionsCallback = null;
/**
* Callback to get extra options for time elements. Defaults to 'timeOptions' in the DO.
*/
var $timeOptionsCallback = null;
/**
* Callback to get extra options for datetime elements. Defaults to 'dateTimeOptions' in the DO.
*/
var $dateTimeOptionsCallback = null;
/**
* These fields will be used when displaying a link record. The fields
* listed will be seperated by ", ". If you specify a link field as a
* display field and linkDisplayLevel is not 0, the link will be followed
* and the display fields of the record linked to displayed within parenthesis.
*
* For example, say we have these tables:
*
* [person]
*
* name = 130
* gender_id = 129
*
* [gender]
* id = 129
* name = 130
*
*
* this link:
*
* [person]
* gender_id = gender:id
*
*
* and this data:
* Person:
* name: "Justin Patrin"
* gender_id: 1
* Gender:
* id: 1
* name: "male"
*
* If person's display fields are:
* <?php
* class DataObject_Person extends DB_DataObject {
* //...
* var $fb_linkDisplayFields = array('name', 'gender_id');
* }
* ?>
*
* and gender's display fields are:
* <?php
* class DataObject_Gender extends DB_DataObject {
* //...
* var $fb_linkDisplayFields = array('name');
* }
* ?>
*
* and we set linkDisplayLevel to 0, the person record will be displayed as:
* "Justin Patrin, 1"
*
* If we set linkDisplayLevel to 1, the person record will be displayed as:
* "Justin Patrin, (male)"
*/
var $linkDisplayFields = array();
/**
* The fields to be used for sorting the options of an auto-generated link
* element. You can specify ASC and DESC in these options as well:
* <?php
* class DataObject_SomeTable extends DB_DataObject {
* //...
* var $fb_linkOrderFields = array('field1', 'field2 DESC');
* }
* ?>
*
* You may also want to escape the field names if they are reserved words in
* the database you're using:
* <?php
* class DataObject_SomeTable extends DB_DataObject {
* //...
* function preGenerateForm() {
* $db = $this->getDatabaseConnection();
* $this->fb_linkOrderFields = array($db->quoteIdentifier('config'),
* $db->quoteIdentifier('select').' DESC');
* }
* }
* ?>
*/
var $linkOrderFields = array();
/**
* If set to true, all links which are renderered as select boxes (see linkElementTypes)
* will have a "--New Value--" option on the bottom which displays a form for the linked
* to table for creating a new record. Upon submit, the sub-form will be checked for
* validity as normal and, if valid, will be used to enter a new record in the linked-to
* table which this record will now link to.
*
* If set to false, all link select boxes will only have entered options (as normal)
* If set to true, all link select boxes will have a New Value entry
* You may also set this to be an array with the names of the link fields to create
* New Value entries for.
*/
var $linkNewValue = array();
/**
* If set to true, all reverseLinks will have a SubForm to create a new linked record.
* Upon submit the sub-form will be checked for validity as normal and, if valid, will
* create a new record in the reverseLink table.
*
* If set to false all reverseLinks will only display existing reverseLink records.
*
* You may also set this to be an array with the names of the reverseLink elements to
* create new linked record sub-forms for.
* You can simply use '__reverseLink_table_field'=>true, or set to an integer to
* display a number of sub-forms.
*/
var $reverseLinkNewValue = array();
/**
* The text which will show up in the link new value select entry. Make sure that this is unique!
*/
var $linkNewValueText = '--New Value--';
/**
* The caption of the submit button, if created.
*/
var $submitText = 'Submit';
/**
* If set to false, no submit button will be created for your forms. Useful when
* used together with QuickForm_Controller when you already have submit buttons
* for next/previous page. By default, a button is being generated.
*/
var $createSubmit = true;
/**
* Array of field labels. The key of the element is the field name. Use this if
* you want to keep the auto-generated elements, but still define your
* own labels for them.
*/
var $fieldLabels = array();
/**
* Array of fields to render elements for. If a field is not given, it will not
* be rendered. If empty, all fields will be rendered (except, normally, the
* primary key).
*/
var $fieldsToRender = array();
/**
* Array of fields which the user can edit. If a field is rendered but not
* specified in this array, it will be frozen. Ignored if not given.
*/
var $userEditableFields = array();
/**
* Array of fields for which no auto-rules may be generated.
* If set to string "__ALL__" or the array includes "__ALL__",
* no rules are automatically generated for any field.
*/
var $excludeFromAutoRules = array();
/**
* Array of fields which will be set required. This is in addition to all
* NOT NULL fields which are automatically set required.
*/
var $fieldsRequired = array();
/**
* Array of groups to put certain elements in. The key is an element name, the
* value is the group to put the element in.
*/
var $preDefGroups = array();
/**
* Indexed array of element names. If defined, this will determine the order
* in which the form elements are being created. This is useful if you're
* using QuickForm's default renderer or dynamic templates and the order of
* the fields in the database doesn't match your needs.
*/
var $preDefOrder = array();
/**
* Array of user-defined QuickForm elements that will be used for the field
* matching the array key. If no match is found, the element for that field
* will be auto-generated. Make your element objects either in the
* preGenerateForm() method or in the getForm() method. Use
* HTML_QuickForm::createElement() to do this.
*
* If you wish to put in a group of elements in place of a single element,
* you can put an array in preDefElements instead of a single element. The
* name of the group will be the name of the replaced element.
*/
var $preDefElements = array();
/**
* An array of the link or date fields which should have an empty option added to the
* select box. This is only a valid option for fields which link to another
* table or date fields.
*/
var $selectAddEmpty = array();
/**
* A string to put in the "empty option" added to select fields
*/
var $selectAddEmptyLabel = '';
/**
* A string to put in the "empty option" added to radio fields
*/
var $radioAddEmptyLabel = '';
/**
* By default, hidden fields are generated for the primary key of a
* DataObject. This behaviour can be deactivated by setting this option to
* false.
*/
var $hidePrimaryKey = true;
/**
* A simple array of field names indicating which of the fields in a particular
* table/class are actually to be treated as textareas. This is an unfortunate
* workaround that is neccessary because the DataObject generator script does
* not make a difference between any other datatypes than string and integer.
* When it does, this can be dropped.
*/
var $textFields = array();
/**
* A simple array of field names indicating which of the fields in a particular
* table/class are actually to be treated date fields. This is an unfortunate
* workaround that is neccessary because the DataObject generator script does
* not make a difference between any other datatypes than string and integer.
* When it does, this can be dropped.
*/
var $dateFields = array();
/**
* A simple array of field names indicating which of the fields in a particular
* table/class are actually to be treated time fields. This is an unfortunate
* workaround that is neccessary because the DataObject generator script does
* not make a difference between any other datatypes than string and integer.
* When it does, this can be dropped.
*/
var $timeFields = array();
/**
* Array to configure the type of the link elements. By default, a select box
* will be used. The key is the name of the link element. The value is 'radio'
* or 'select'. If you choose 'radio', radio buttons will be made instead of
* a select box.
*/
var $linkElementTypes = array();
/**
* A simple array of fields names which should be treated as ENUMs. A select
* box will be created with the enum options. If you add this field to the
* linkElementTypes array and give it a 'radio' type, you will get radio buttons
* instead.
*
* The default handler for enums is only tested in mysql. If you are using a
* different DB backend, use enumOptionsCallback or enumOptions.
*/
var $enumFields = array();
/**
* A valid callback which will return the options in a simple array of strings
* for an enum field given the table and field names.
*/
var $enumOptionsCallback = array();
/**
* An array which holds enum options for specific fields. Each key should be a
* field in the current table and each value holds a an array of strings which
* are the possible values for the enum. This will only be used if the field is
* listed in enumFields.
*/
var $enumOptions = array();
/**
* An array which holds the field names of those fields which are booleans.
* They will be displayed as checkboxes.
*/
var $booleanFields = array();
/**
* The text to put between crosslink elements.
*/
var $crossLinkSeparator = '<br/>';
/**
* If this is set to 1 or above, links will be followed in the display fields
* and the display fields of the record linked to will be used for display.
* If this is set to 2, links will be followed in the linked record as well.
* This can be set to any number of links you wish but could easily slow down
* your application if set to more than 1 or 2 (but only if you have links in
* your display fields that go that far ;-)). For a more in-depth example, see
* the docs for linkDisplayFields.
*/
var $linkDisplayLevel = 0;
/**
* The crossLinks array holds data pertaining to many-many links. If you
* have a table which links two tables together, you can use this to
* automatically create a set of checkboxes or a multi-select on your form.
* The simplest way of using this is:
* <code>
* <?php
* class DataObject_SomeTable extends DB_DataObject {
* //...
* var $fb_crossLinks = array(array('table' => 'crossLinkTable'));
* }
* ?>
* </code>
* Where crossLinkTable is the name of the linking table. You can have as
* many cross-link entries as you want. Try it with just the table ewntry
* first. If it doesn't work, you can specify the fields to use as well.
* <code>
* 'fromField' => 'linkFieldToCurrentTable' //This is the field which links to the current (from) table
* 'toField' => 'linkFieldToLinkedTable' //This is the field which links to the "other" (to) table
* </code>
* To get a multi-select add a 'type' key which it set to 'select'.
* <code>
* <?php
* class DataObject_SomeTable extends DB_DataObject {
* //...
* var $fb_crossLinks = array(array('table' => 'crossLinkTable', 'type' => 'select'));
* }
* ?>
* </code>
* An example: I have a user table and a group table, each with a primary
* key called id. There is a table called user_group which has fields user_id
* and group_id which are set up as links to user and group. Here's the
* configuration array that could go in both the user DO and the group DO:
* <code>
* <?php
* $fb_crossLinks = array(array('table' => 'user_group'));
* ?>
* </code>
* Here is the full configuration for the user DO:
* <code>
* <?php
* $fb_crossLinks = array(array('table' => 'user_group',
* 'fromField' => 'user_id',
* 'toField' => 'group_id'));
* ?>
* </code>
* And the full configuration for the group DO:
* <code>
* <?php
* $fb_crossLinks = array(array('table' => 'user_group',
* 'fromField' => 'group_id',
* 'toField' => 'user_id'));
* ?>
* </code>
*
* crossLinks can also be automatically collapsed only to the selected
* records by setting the 'collapse' key to true. The collapsed options
* can be viewed again by clicking the "Show All" link.
*
* You can also specify the seperator between the elements with crossLinkSeperator.
*/
var $crossLinks = array();
/**
* You can also specify extra fields to edit in the a crossLink table with
* this option. For example, if the user_group table mentioned above had
* another field called 'role' which was a text field, you could specify it
* like this in the user_group DataObject class:
* <code>
* <?php
* class DataObject_User_group extends DB_DataObject {
* //normal stuff here
*
* var $fb_crossLinkExtraFields = array('role');
* }
* ?>
* </code>
*
* This would cause a text box to show up next to each checkbox in the
* user_group section of the form for the field 'role'.
*
* You can specify as many fields as you want in the 'extraFields' array.
*
* Note: If you specify a linked field in 'extraFields' you'll get a select
* box just like when you do a normal link field in a FormBuilder form. :-)
*/
var $crossLinkExtraFields = array();
/**
* Holds triple link data.
* The tripleLinks array can be used to display checkboxes for "triple-links". A triple link is set
* up with a table which links to three different tables. These will show up as a table of checkboxes
* The initial setting (table) is the same as for crossLinks. The field configuration keys (if you
* need them) are:
* <code>
* 'fromField'
* 'toField1'
* 'toField2'
* </code>
*/
var $tripleLinks = array();
/**
* Holds reverseLink configuration.
* A reverseLink is a table which links back to the current table. For
* example, let say we have a "gender" table which has Male and Female in it
* and a "person" table which has the fields "name", which holds the person's
* name and "genre_id" which links to the genre. If you set up a form for the
* gender table, the person table can be a reverseLink. The setup in the
* gender table would look like this:
* <code>
* <?php
* class DataObject_Gender extends DB_DataObject {
* //normal stuff here
*
* var $fb_reverseLinks = array(array('table' => 'person'));
* }
* ?>
* </code>
* Now a list of people will be shown in the gender form with a checkbox next
* to it which is checked if the record currently links to the one you're
* editing. In addition, some special text will be added to the end of the
* label for the person record if it's linked to another gender.
*
* Say we have a person record with the name "Justin Patrin" which is linked
* to the gender "Male". If you view the form for the gender "Male", the
* checkbox next to "Justin Patrin" will be checked. If you choose the
* "Female" gender the checkbox will be unchecked and it will say:
* Justin Patrin - currently linked to - Male
*
* If the link field is set as NOT NULL then FormBuilder will not process
* an unchecked checkbox unless you specify a default value to set the link
* to. If null is allowed, the link will be set to NULL. To specify a default
* value:
* <code>
* <?php
* class DataObject_Gender extends DB_DataObject {
* //normal stuff here
*
* var $fb_reverseLinks = array(array('table' => 'person',
* 'defaultLinkValue' => 5));
* }
* ?>
* </code>
* Now if a checkbox is unchecked the link field will be set to 5...whatever
* that means. Be careful here as you need to make sure you enter the correct
* value here (probably the value of the primary key of the record you want
* to link to by default).
*
* You may also set the text which is displayed between the record and the
* currently linked to record.
* <code>
* <?php
* class DataObject_Gender extends DB_DataObject {
* //normal stuff here
*
* var $fb_reverseLinks = array(array('table' => 'person',
* 'linkText' => ' is currently listed as a '));
* }
* ?>
* </code>
* If you select "Female" the Justin Patrin entry would now say:
* Justin Patrin__ is currently listed as a Male__
*/
var $reverseLinks = array();
/**
* If set to true, validation rules will also be client side.
*/
var $clientRules = false;
/**
* A string to prepend to element names. Together with elementNamePostfix, this option allows you to
* alter the form element names that FormBuilder uses to create and process elements. The main use for
* this is to combine multiple forms into one. For example, if you wanted to use multiple FB forms for
* the same table within one actual HTML form you could do something like this:
* <?php
* $do = DB_DataObject::factory('table');
* $fb = DB_DataObject_FormBuilder::create($do);
* $fb->elementNamePrefix = 'formOne';
* $form = $fb->getForm();
*
* $do2 = DB_DataObject::factory('table');
* $fb2 = DB_DataObject_FormBuilder::create($do2);
* $fb->elementNamePrefix = 'formTwo';
* $fb->useForm($form);
* $form = $fb->getForm();
*
* //normal processing here
* ?>
*
* If you assume that "table: has one field, "name", then the resultant form will have two elements:
* "formOnename" and "formTwoname".
*
* Please note: You *cannot* use '[' or ']' anywhere in the prefix or postfix. Doing so
* will cause FormBuilder to not be able to process the form.
*/
var $elementNamePrefix = '';
/**
* A postfix to put after element names in the form
* @see DB_DataObject_FormBuilder::elementNamePrefix
*/
var $elementNamePostfix = '';
/**
* Whether or not to use call-time-pass-by-reference when calling DataObject callbacks
*/
var $useCallTimePassByReference = false;
/**
* The callback to use for preGenerateForm calls. Defaults to preGenerateForm() in the DataObject.
*/
var $preGenerateFormCallback = null;
/**
* The callback to use for postGenerateForm calls. Defaults to postGenerateForm() in the DataObject.
*/
var $postGenerateFormCallback = null;
/**
* The callback to use for preProcessForm calls. Defaults to preProcessForm() in the DataObject.
*/
var $preProcessFormCallback = null;
/**
* The callback to use for postProcessForm calls. Defaults to postProcessForm() in the DataObject.
*/
var $postProcessFormCallback = null;
/**
* The callback to use for prepareLinkedDataObject calls. Defaults to prepareLinkedDataObject() in the DataObject.
*/
var $prepareLinkedDataObjectCallback = null;
/**
* Array to determine what QuickForm element types are being used for which
* general field types. If you configure FormBuilder using arrays, the format is:
* array('nameOfFieldType' => 'QuickForm_Element_name', ...);
* If configured via .ini file, the format looks like this:
* elementTypeMap = shorttext:text,date:date,...
*
* Allowed field types:
* <ul><li>shorttext</li>
* <li>longtext</<li>
* <li>date</li>
* <li>integer</li>
* <li>float</li></ul>
*/
var $elementTypeMap = array('shorttext' => 'text',
'longtext' => 'textarea',
'date' => 'date',
'time' => 'date',
'datetime' => 'date',
'integer' => 'text',
'float' => 'text',
'select' => 'select',
'multiselect' => 'select',
'popupSelect' => 'popupSelect',
'elementGrid' => 'elementGrid');
/**
* Array of attributes for each element type. See the keys of elementTypeMap
* for the allowed element types.
*
* The key is the element type. The value can be a valid attribute string or
* an associative array of attributes.
*/
var $elementTypeAttributes = array();
/**
* Array of attributes for each specific field.
*
* The key is the field name. The value can be a valid attribute string or
* an associative array of attributes.
*/
var $fieldAttributes = array();
/**
* Set to true to use accessor methods (getters) for accessing field values, if they exist.
*
* If this is set to true and a method exists of the name getFieldName where
* FieldName is the name of the field then it will be called to get the value
* of the field.
*
* Note: The following field names will not work with getters due to function collisions
* in DB_DataObject. Do not use fields with these names in conjunction with useAccessors.
* * Link
* * Links
* * LinkArray
* * DatabaseConnection
* * DatabaseResult
*
* Note: Accessors may not be used to get link field values. Link field values are
* internal to a database and are assumed not to need accessors.
*/
var $useAccessors = false;
/**
* Set to true to use mutator methods (setters) for setting field values, if they exist.
*
* If this is set to true and a method exists of the name setFieldName where
* FieldName is the name of the field then it will be called to set the value
* of the field.
*
* Note: Do not use a field named From if you use this option. DB_DataObject
* has a default method setFrom which will cause problems.
*
* Note: Mutators may not be used to set link fields. Link field values are internal
* to a database and are assumed not to need mutators.
*/
var $useMutators = false;
/**
* DB_DataObject_FormBuilder::create()
*
* Factory method. As this is meant as an abstract class, it is the only supported
* method to make a new object instance. Pass the DataObject-derived class you want to
* build a form from as the first parameter. Use the second to pass additional options.
*
* Options can be any option for FormBuilder (see properties which do not start with _)
*
* The third parameter is the name of a driver class. A driver class will take care of
* the actual form generation. This way it's possible to have FormBuilder build different
* forms for different types of output media from the same set of DataObjects.
*
* Currently available driver classes:
* - QuickForm (stable)
* - XUL (experimental!)
*
* @param object $do The DB_DataObject-derived object for which a form shall be built
* @param array $options An optional associative array of options.
* @param string $driver Optional: Name of the driver class for constructing the form object. Default: QuickForm.
* @access public
* @returns object DB_DataObject_FormBuilder or PEAR_Error object
*/
function &create(&$do, $options = false, $driver = 'QuickForm', $mainClass = 'DB_DataObject_FormBuilder')
{
if (!is_a($do, 'db_dataobject')) {
$err =& PEAR::raiseError('DB_DataObject_FormBuilder::create(): Object does not extend DB_DataObject.',
DB_DATAOBJECT_FORMBUILDER_ERROR_NODATAOBJECT);
return $err;
}
if (!class_exists($mainClass)) {
$err =& PEAR::raiseError('DB_DataObject_FormBuilder::create(): Main class "'.$mainClass.'" not found',
DB_DATAOBJECT_FORMBUILDER_ERROR_UNKNOWNDRIVER);
return $err;
}
$fb =& new $mainClass($do, $options);
$className = 'DB_DataObject_FormBuilder_'. $driver;
$fileName = 'DB/DataObject/FormBuilder/'.$driver.'.php';
if (!class_exists($className)) {
/*$exists = false;
foreach (split(PATH_SEPARATOR, get_include_path()) as $path) {
if (file_exists($path.'/'.$fileName)
&& is_readable($path.'/'.$fileName)) {
$exists = true;
break;
}
}*/
$fp = @fopen($fileName, 'r', true);
if ($fp === false) {
$err =& PEAR::raiseError('DB_DataObject_FormBuilder::create(): File "'.$fileName.
'" for driver class "'.$className.'" not found or not readable.',
DB_DATAOBJECT_FORMBUILDER_ERROR_UNKNOWNDRIVER);
return $err;
}
fclose($fp);
include_once($fileName);
if (!class_exists($className)) {
$err =& PEAR::raiseError('DB_DataObject_FormBuilder::create(): Driver class "'.$className.
'" not found after including "'.$fileName.'".',
DB_DATAOBJECT_FORMBUILDER_ERROR_UNKNOWNDRIVER);
return $err;
}
}
$fb->_form =& new $className($fb);
return $fb;
}
/**
* DB_DataObject_FormBuilder::DB_DataObject_FormBuilder()
*
* The class constructor.
*
* @param object $do The DB_DataObject-derived object for which a form shall be built
* @param array $options An optional associative array of options.
* @access public
*/
function DB_DataObject_FormBuilder(&$do, $options = false)
{
$this->_do =& $do;
$this->preGenerateFormCallback = array(&$this->_do, 'preGenerateForm');
$this->postGenerateFormCallback = array(&$this->_do, 'postGenerateForm');
$this->preProcessFormCallback = array(&$this->_do, 'preProcessForm');
$this->postProcessFormCallback = array(&$this->_do, 'postProcessForm');
$this->prepareLinkedDataObjectCallback = array(&$this->_do, 'prepareLinkedDataObject');
$this->dateOptionsCallback = array(&$this->_do, 'dateOptions');
$this->timeOptionsCallback = array(&$this->_do, 'timeOptions');
$this->dateTimeOptionsCallback = array(&$this->_do, 'dateTimeOptions');
$this->enumOptionsCallback = array(&$this, '_getEnumOptions');
// Read in config
$vars = get_object_vars($this);
$defVars = get_class_vars(get_class($this));
$config =& PEAR::getStaticProperty('DB_DataObject_FormBuilder', 'options');
if (!isset($config) || !is_array($config)) {
$config = array();
}
//read all config options into member vars
foreach ($config as $key => $value) {
if (in_array($key, $vars) && $key[0] != '_') {
if (isset($defVars[$key])
&& is_array($defVars[$key])
&& is_string($value)) {
$value = $this->_explodeArrString($value);
}
$this->$key = $value;
}
}
if (is_array($options)) {
reset($options);
while (list($key, $value) = each($options)) {
if (in_array($key, $vars) && $key[0] != '_') {
$this->$key = $value;
}
}
}
// Check whether we now got valid callbacks for some callback properties,
// otherwise correct them
foreach(array('dateFromDatabaseCallback', 'dateToDatabaseCallback', 'enumOptionsCallback') as $callback) {
if (!$this->isCallableAndExists($this->$callback)) {
if (is_array($this->$callback)
&& count($this->$callback) == 1
&& $this->isCallableAndExists($this->{$callback}[0])) {
// Probably got messed up by _explodeArrString()
$this->$callback = $this->{$callback}[0];
}
}
}
}
/**
* Gets the primary key field name for a DataObject
* Looks for $do->_primary_key, $do->sequenceKey(), then $do->keys()
*
* @param DB_DataObject the DataObject to get the primary key of
* @return string the name of the primary key or false if none is found
*/
function _getPrimaryKey(&$do) {
if (isset($do->_primary_key) && strlen($do->_primary_key)) {
return $do->_primary_key;
} elseif (($seq = $do->sequenceKey()) && isset($seq[0]) && strlen($seq[0])) {
return $seq[0];
} else {
if (($keys = $do->keys()) && isset($keys[0]) && strlen($keys[0])) {
return $keys[0];
}
}
DB_DataObject_FormBuilder::debug('Error: Primary Key not found for table '.$do->tableName());
return false;
}
function _sanitizeFieldName($field) {
return preg_replace('/[^a-z_]/Si', '_', $field);
}
/**
* DB_DataObject_FormBuilder::_getEnumOptions()
* Gets the possible values for an enum field from the DB. This is only tested in
* mysql and will likely break on all other DB backends.
*
* @param string Table to query on
* @param string Field to get enum options for
* @return array array of strings, each being a possible value for th eenum field
*/
function _getEnumOptions($table, $field) {
$db = $this->_do->getDatabaseConnection();
if (isset($GLOBALS['_DB_DATAOBJECT']['CONFIG']['quote_identifiers'])
&& $GLOBALS['_DB_DATAOBJECT']['CONFIG']['quote_identifiers']) {
$table = $db->quoteIdentifier($table);
}
if (!isset($GLOBALS['_DB_DATAOBJECT']['CONFIG']['db_driver'])
|| $GLOBALS['_DB_DATAOBJECT']['CONFIG']['db_driver'] == 'DB') {
$option = $db->getRow('SHOW COLUMNS FROM '.$table.' LIKE '.$db->quoteSmart($field),
DB_FETCHMODE_ASSOC);
} else {
$option = $db->queryRow('SHOW COLUMNS FROM '.$table.' LIKE '.$db->quote($field),
null,
MDB2_FETCHMODE_ASSOC);
}
if (PEAR::isError($option)) {
return PEAR::raiseError('There was an error querying for the enum options for field "'.$field.'". You likely need to use enumOptionsCallback.', null, null, null, $option);
}
foreach ($option as $key => $value) {
if (strtolower($key) == 'type') {
$option = $value;
break;
}
}
if (is_array($option)) {
return PEAR::raiseError('There was an error querying for the enum options for field "'.$field.'". You likely need to use enumOptionsCallback.');
}
$option = substr($option, strpos($option, '(') + 1);
$option = substr($option, 0, strrpos($option, ')') - strlen($option));
$split = explode(',', $option);
$options = array();
$option = '';
for ($i = 0; $i < sizeof($split); ++$i) {
$option .= $split[$i];
if (substr_count($option, "'") % 2 == 0) {
$option = trim(trim($option), "'");
$options[$option] = $option;
$option = '';
}
}
return $options;
}
/**
* DB_DataObject_FormBuilder::_generateForm()
*
* Builds a simple HTML form for the current DataObject. Internal function, called by
* the public getForm() method. You can override this in child classes if needed, but
* it's also possible to leave this as it is and just override the getForm() method
* to simply fine-tune the auto-generated form object (i.e. add/remove elements, alter
* options, add/remove rules etc.).
* If a key with the same name as the current field is found in the fb_preDefElements
* property, the QuickForm element object contained in that array will be used instead
* of auto-generating a new one. This allows for complete step-by-step customizing of
* your forms.
*
* Note for date fields: HTML_QuickForm allows passing of an options array to the
* HTML_QuickForm_date element. You can define your own options array for date elements
* in your DataObject-derived classes by defining a method "dateOptions($fieldName)".
* FormBuilder will call that method whenever it encounters a date field and expects to
* get back a valid options array.
*
* @param string $action The form action. Optional. If set to false (default), PHP_SELF is used.
* @param string $target The window target of the form. Optional. Defaults to '_self'.
* @param string $formName The name of the form, will be used in "id" and "name" attributes. If set to false (default), the class name is used
* @param string $method The submit method. Defaults to 'post'.
* @return object
* @access protected
* @author Markus Wolff <mw21st@php.net>
* @author Fabien Franzen <atelierfabien@home.nl>
*/
function &_generateForm($action = false, $target = '_self', $formName = false, $method = 'post')
{
if ($formName === false) {
$formName = strtolower(get_class($this->_do));
}
if ($action === false) {
$action = $_SERVER['PHP_SELF'];
}
// Retrieve the form object to use (may depend on the current renderer)
$this->_form->_createFormObject($formName, $method, $action, $target);
// Initialize array with default values
//$formValues = $this->_do->toArray();
if ($this->addFormHeader) {
// Add a header to the form - set addFormHeader property to false to prevent this
$this->_form->_addFormHeader(is_null($this->formHeaderText) ? $this->prettyName($this->_do->tableName()) : $this->formHeaderText);
}
// Go through all table fields and create appropriate form elements
$keys = $this->_do->keys();
// Reorder elements if requested, will return _getFieldsToRender if no reordering is needed
$elements = $this->_reorderElements();
//get elements to freeze
$user_editable_fields = $this->_getUserEditableFields();
if (is_array($user_editable_fields)) {
$elements_to_freeze = array_diff(array_keys($elements), $user_editable_fields);
} else {
$elements_to_freeze = array();
}
if (!is_array($links = $this->_do->links())) {
$links = array();
}
$pk = $this->_getPrimaryKey($this->_do);
$rules = array();
foreach ($elements as $key => $type) {
// Check if current field is primary key. And primary key hiding is on. If so, make hidden field
if (in_array($key, $keys) && $this->hidePrimaryKey == true) {
$formValues[$key] = $this->_do->$key;
$element =& $this->_form->_createHiddenField($key);
} else {
unset($element);
// Try to determine field types depending on object properties
$notNull = $type & DB_DATAOBJECT_NOTNULL;
if (in_array($key, $this->dateFields)) {
$type = DB_DATAOBJECT_DATE;
} elseif (in_array($key, $this->timeFields)) {
$type = DB_DATAOBJECT_TIME;
} elseif (in_array($key, $this->textFields)) {
$type = DB_DATAOBJECT_TXT;
} elseif (in_array($key, $this->enumFields)) {
$type = DB_DATAOBJECT_FORMBUILDER_ENUM;
} elseif (in_array($key, $this->booleanFields)) {
$type = DB_DATAOBJECT_BOOL;
}
if ($notNull || in_array($key, $this->fieldsRequired)) {
$type |= DB_DATAOBJECT_NOTNULL;
}
if (isset($this->preDefElements[$key])
&& (is_object($this->preDefElements[$key])
|| is_array($this->preDefElements[$key]))) {
// Use predefined form field, IMPORTANT: This may depend on the used renderer!!
$element =& $this->preDefElements[$key];
} elseif (isset($links[$key])) {
// If this field links to another table, display selectbox or radiobuttons
$isRadio = isset($this->linkElementTypes[$key]) && $this->linkElementTypes[$key] == 'radio';
$opt = $this->getSelectOptions($key,
false,
!($type & DB_DATAOBJECT_NOTNULL),
$isRadio ? $this->radioAddEmptyLabel : $this->selectAddEmptyLabel);
if ($isRadio) {
$element =& $this->_form->_createRadioButtons($key, $opt);
} else {
$element =& $this->_form->_createSelectBox($key, $opt);
}
unset($opt);
}
// No predefined object available, auto-generate new one
$elValidator = false;
$elValidRule = false;
// Auto-detect field types depending on field's database type
switch (true) {
case ($type & DB_DATAOBJECT_BOOL):
if ($this->useAccessors
&& method_exists($this->_do, 'get' . $key)) {
$formValues[$key] = $this->_do->{'get'.$key}();
} else {
$formValues[$key] = $this->_do->$key;
}
if ($formValues[$key] === 'f') {
$formValues[$key] = 0;
}
if (!isset($element)) {
$element =& $this->_form->_createCheckbox($key, null, null, $this->getFieldLabel($key));
}
break;
case ($type & DB_DATAOBJECT_INT):
if ($this->useAccessors
&& method_exists($this->_do, 'get' . $key)) {
$formValues[$key] = $this->_do->{'get'.$key}();
} else {
$formValues[$key] = $this->_do->$key;
}
if (!isset($element)) {
$element =& $this->_form->_createIntegerField($key);
$elValidator = 'numeric';
}
break;
case (($type & DB_DATAOBJECT_DATE) && ($type & DB_DATAOBJECT_TIME)):
$this->debug('DATE & TIME CONVERSION using callback for element '.$key.' ('.$this->_do->$key.')!', 'FormBuilder');
if ($this->useAccessors
&& method_exists($this->_do, 'get' . $key)) {
$fieldValue = $this->_do->{'get'.$key}();
} else {
$fieldValue = $this->_do->$key;
}
if ($this->isCallableAndExists($this->dateFromDatabaseCallback)) {
$formValues[$key] = call_user_func($this->dateFromDatabaseCallback, $fieldValue);
} else {
$this->debug('WARNING: dateFromDatabaseCallback callback not callable', 'FormBuilder');
$formValues[$key] = $fieldValue;
}
if (!isset($element)) {
$element =& $this->_form->_createDateTimeElement($key);
}
break;
case ($type & DB_DATAOBJECT_DATE):
$this->debug('DATE CONVERSION using callback for element '.$key.' ('.$this->_do->$key.')!', 'FormBuilder');
if ($this->useAccessors
&& method_exists($this->_do, 'get' . $key)) {
$fieldValue = $this->_do->{'get'.$key}();
} else {
$fieldValue = $this->_do->$key;
}
if ($this->isCallableAndExists($this->dateFromDatabaseCallback)) {
$formValues[$key] = call_user_func($this->dateFromDatabaseCallback, $fieldValue);
} else {
$this->debug('WARNING: dateFromDatabaseCallback callback not callable', 'FormBuilder');
$formValues[$key] = $fieldValue;
}
if (!isset($element)) {
$element =& $this->_form->_createDateElement($key);
}
break;
case ($type & DB_DATAOBJECT_TIME):
$this->debug('TIME CONVERSION using callback for element '.$key.' ('.$this->_do->$key.')!', 'FormBuilder');
if ($this->useAccessors
&& method_exists($this->_do, 'get' . $key)) {
$fieldValue = $this->_do->{'get'.$key}();
} else {
$fieldValue = $this->_do->$key;
}
if ($this->isCallableAndExists($this->dateFromDatabaseCallback)) {
$formValues[$key] = call_user_func($this->dateFromDatabaseCallback, $fieldValue);
} else {
$this->debug('WARNING: dateFromDatabaseCallback callback not callable', 'FormBuilder');
$formValues[$key] = $fieldValue;
}
if (!isset($element)) {
$element =& $this->_form->_createTimeElement($key);
}
break;
case ($type & DB_DATAOBJECT_TXT || $type & DB_DATAOBJECT_BLOB):
if ($this->useAccessors
&& method_exists($this->_do, 'get' . $key)) {
$formValues[$key] = $this->_do->{'get'.$key}();
} else {
$formValues[$key] = $this->_do->$key;
}
if (!isset($element)) {
$element =& $this->_form->_createTextArea($key);
}
break;
case ($type & DB_DATAOBJECT_STR):
if ($this->useAccessors
&& method_exists($this->_do, 'get' . $key)) {
$formValues[$key] = $this->_do->{'get'.$key}();
} else {
$formValues[$key] = $this->_do->$key;
}
if (!isset($element)) {
// If field content contains linebreaks, make textarea - otherwise, standard textbox
if (isset($this->_do->$key) && strlen($this->_do->$key) && strstr($this->_do->$key, "\n")) {
$element =& $this->_form->_createTextArea($key);
} else {
$element =& $this->_form->_createTextField($key);
}
}
break;
case ($type & DB_DATAOBJECT_FORMBUILDER_CROSSLINK):
unset($element);
// generate crossLink stuff
/*if ($pk === false) {
return PEAR::raiseError('A primary key must exist in the base table when using crossLinks.');
}*/
$crossLink = $this->crossLinks[$key];
$groupName = $this->_sanitizeFieldName('__crossLink_'.$crossLink['table'].
'_'.$crossLink['fromField'].
'_'.$crossLink['toField']);
unset($crossLinkDo);
$crossLinkDo = DB_DataObject::factory($crossLink['table']);
if (PEAR::isError($crossLinkDo)) {
die($crossLinkDo->getMessage());
}
if (!is_array($crossLinkLinks = $crossLinkDo->links())) {
$crossLinkLinks = array();
}
list($linkedtable, $linkedfield) = explode(':', $crossLinkLinks[$crossLink['toField']]);
list($fromtable, $fromfield) = explode(':', $crossLinkLinks[$crossLink['fromField']]);
//if ($fromtable !== $this->_do->tableName()) error?
$all_options = $this->_getSelectOptions($linkedtable, false, false, false, $linkedfield);
$selected_options = array();
if (isset($this->_do->$fromfield)) {
$crossLinkDo->{$crossLink['fromField']} = $this->_do->$fromfield;
if ($this->isCallableAndExists($this->prepareLinkedDataObjectCallback)) {
call_user_func_array($this->prepareLinkedDataObjectCallback, array(&$crossLinkDo, $key));
}
if ($crossLinkDo->find() > 0) {
while ($crossLinkDo->fetch()) {
$selected_options[$crossLinkDo->{$crossLink['toField']}] = clone($crossLinkDo);
}
}
}
if (isset($crossLink['type']) && $crossLink['type'] == 'select') {
unset($element);
$element =& $this->_form->_createSelectBox($groupName, $all_options, true);
$formValues[$groupName] = array_keys($selected_options); // set defaults later
// ***X*** generate checkboxes
} else {
$element = array();
$rowNames = array();
foreach ($all_options as $optionKey => $value) {
if (isset($selected_options[$optionKey])) {
if (!isset($formValues[$groupName])) {
$formValues[$groupName] = array();
}
$formValues[$groupName][$optionKey] = $optionKey;
}
$crossLinkElement = $this->_form->_createCheckbox($groupName.'['.$optionKey.']', $value, $optionKey);
$elementNamePrefix = $this->elementNamePrefix.$groupName.'__'.$optionKey.'_';
$elementNamePostfix = '_'.$this->elementNamePostfix;//']';
if (isset($crossLinkDo->fb_crossLinkExtraFields)) {
$row = array(&$crossLinkElement);
if (isset($selected_options[$optionKey])) {
$extraFieldDo = $selected_options[$optionKey];
} else {
unset($extraFieldDo);
$extraFieldDo = DB_DataObject::factory($crossLink['table']);
}
unset($tempFb);
$tempFb =& DB_DataObject_FormBuilder::create($extraFieldDo,
false,
'QuickForm',
get_class($this));
$extraFieldDo->fb_fieldsToRender = $crossLinkDo->fb_crossLinkExtraFields;
$extraFieldDo->fb_elementNamePrefix = $elementNamePrefix;
$extraFieldDo->fb_elementNamePostfix = $elementNamePostfix;
$extraFieldDo->fb_linkNewValue = false;
$this->_extraFieldsFb[$elementNamePrefix.$elementNamePostfix] =& $tempFb;
$tempForm = $tempFb->getForm();
$colNames = array('');
foreach ($crossLinkDo->fb_crossLinkExtraFields as $extraField) {
if ($tempForm->elementExists($elementNamePrefix.$extraField.$elementNamePostfix)) {
$tempEl =& $tempForm->getElement($elementNamePrefix.$extraField.$elementNamePostfix);
$colNames[$extraField] = $tempEl->getLabel();
} else {
$tempEl =& $this->_form->_createStaticField($elementNamePrefix.$extraField.$elementNamePostfix,
'Error - element not found for extra field '.$extraField);
}
$row[] =& $tempEl;
if (!isset($formValues[$groupName.'__extraFields'])) {
$formValues[$groupName.'__extraFields'] = array();
}
if (!isset($formValues[$groupName.'__extraFields'][$optionKey])) {
$formValues[$groupName.'__extraFields'][$optionKey] = array();
}
$formValues[$groupName.'__extraFields'][$optionKey][$extraField] = $tempEl->getValue();
unset($tempEl);
}
$element[] = $row;
unset($tempFb, $tempForm, $extraFieldDo, $row);
$rowNames[] = '<label for="'.$crossLinkElement->getAttribute('id').'">'.$value.'</label>';
$crossLinkElement->setText('');
} elseif ($crossLink['collapse']) {
$element[] = array(&$crossLinkElement);
$rowNames[] = '<label for="'.$crossLinkElement->getAttribute('id').'">'.$value.'</label>';
$crossLinkElement->setText('');
$colNames = array();
} else {
$element[] =& $crossLinkElement;
}
unset($crossLinkElement);
}
if (isset($crossLinkDo->fb_crossLinkExtraFields) || $crossLink['collapse']) {
$this->_form->_addElementGrid($groupName, array_values($colNames), $rowNames, $element);
} else {
$this->_form->_addElementGroup($element, $groupName, $this->crossLinkSeparator);
}
if ($crossLink['collapse']) {
$this->_form->_collapseRecordList($groupName);
}
unset($element);
unset($rowNames);
unset($colNames);
}
break;
case ($type & DB_DATAOBJECT_FORMBUILDER_TRIPLELINK):
unset($element);
/*if ($pk === false) {
return PEAR::raiseError('A primary key must exist in the base table when using tripleLinks.');
}*/
$tripleLink = $this->tripleLinks[$key];
$elName = $this->_sanitizeFieldName('__tripleLink_'.$tripleLink['table'].
'_'.$tripleLink['fromField'].
'_'.$tripleLink['toField1'].
'_'.$tripleLink['toField2']);
$freeze = array_search($elName, $elements_to_freeze);
unset($tripleLinkDo);
$tripleLinkDo = DB_DataObject::factory($tripleLink['table']);
if (PEAR::isError($tripleLinkDo)) {
die($tripleLinkDo->getMessage());
}
if (!is_array($tripleLinkLinks = $tripleLinkDo->links())) {
$tripleLinkLinks = array();
}
list($linkedtable1, $linkedfield1) = explode(':', $tripleLinkLinks[$tripleLink['toField1']]);
list($linkedtable2, $linkedfield2) = explode(':', $tripleLinkLinks[$tripleLink['toField2']]);
list($fromtable, $fromfield) = explode(':', $tripleLinkLinks[$tripleLink['fromField']]);
//if ($fromtable !== $this->_do->tableName()) error?
$all_options1 = $this->_getSelectOptions($linkedtable1, false, false, false, $linkedfield1);
$all_options2 = $this->_getSelectOptions($linkedtable2, false, false, false, $linkedfield2);
$selected_options = array();
if (isset($this->_do->$fromfield)) {
$tripleLinkDo->{$tripleLink['fromField']} = $this->_do->$fromfield;
if ($this->isCallableAndExists($this->prepareLinkedDataObjectCallback)) {
call_user_func_array($this->prepareLinkedDataObjectCallback, array(&$tripleLinkDo, $key));
}
if ($tripleLinkDo->find() > 0) {
while ($tripleLinkDo->fetch()) {
$selected_options[$tripleLinkDo->{$tripleLink['toField1']}][] = $tripleLinkDo->{$tripleLink['toField2']};
}
}
}
$columnNames = array();
foreach ($all_options2 as $key2 => $value2) {
$columnNames[] = $value2;
}
$rows = array();
$rowNames = array();
$formValues[$key] = array();
foreach ($all_options1 as $key1 => $value1) {
$rowNames[] = $value1;
$row = array();
foreach ($all_options2 as $key2 => $value2) {
unset($tripleLinkElement);
$tripleLinkElement = $this->_form->_createCheckbox($elName.'['.$key1.']['.$key2.']',
'',
$key2
//false,
//$freeze
);
if (isset($selected_options[$key1])) {
if (in_array($key2, $selected_options[$key1])) {
$tripleLinkName = '__tripleLink_'.$tripleLink['table'].
'_'.$tripleLink['fromField'].
'_'.$tripleLink['toField1'].
'_'.$tripleLink['toField2'];
if (!isset($formValues[$tripleLinkName][$key1])) {
$formValues[$tripleLinkName][$key1] = array();
}
$formValues[$tripleLinkName][$key1][$key2] = $key2;
}
}
$row[] =& $tripleLinkElement;
}
$rows[] =& $row;
unset($row);
}
$this->_form->_addElementGrid($elName, $columnNames, $rowNames, $rows);
unset($columnNames, $rowNames, $rows);
break;
case ($type & DB_DATAOBJECT_FORMBUILDER_ENUM):
if ($this->useAccessors
&& method_exists($this->_do, 'get' . $key)) {
$formValues[$key] = $this->_do->{'get'.$key}();
} else {
$formValues[$key] = $this->_do->$key;
}
if (!isset($element)) {
$isRadio = isset($this->linkElementTypes[$key])
&& $this->linkElementTypes[$key] == 'radio';
if (isset($this->enumOptions[$key])) {
$options = $this->enumOptions[$key];
} else {
if ($this->isCallableAndExists($this->enumOptionsCallback)) {
$options = call_user_func($this->enumOptionsCallback, $this->_do->__table, $key);
} else {
$options =& PEAR::raiseError('enumOptionsCallback is an invalid callback');
}
if (PEAR::isError($options)) {
return $options;
}
}
/*if (array_keys($options) === range(0, count($options)-1)) {
$newOptions = array();
foreach ($options as $value) {
$newOptions[$value] = $value;
}
$options = $newOptions;
}*/
if (in_array($key, $this->selectAddEmpty)
|| (!($type & DB_DATAOBJECT_NOTNULL) && !isset($options['']))) {
$options = array('' => ($isRadio
? $this->radioAddEmptyLabel
: $this->selectAddEmptyLabel))
+ $options;
}
if (!$options) {
return PEAR::raiseError('There are no options defined for the enum field "'.$key.'". You may need to set the options in the enumOptions option or use your own enumOptionsCallback.');
}
$element = array();
if ($isRadio) {
$element =& $this->_form->_createRadioButtons($key, $options);
} else {
$element =& $this->_form->_createSelectBox($key, $options);
}
unset($options);
}
break;
case ($type & DB_DATAOBJECT_FORMBUILDER_REVERSELINK):
unset($element);
$element = array();
$elName = $this->_sanitizeFieldName('__reverseLink_'.$this->reverseLinks[$key]['table'].'_'.$this->reverseLinks[$key]['field']);
unset($do);
$do = DB_DataObject::factory($this->reverseLinks[$key]['table']);
if ($this->isCallableAndExists($this->prepareLinkedDataObjectCallback)) {
call_user_func_array($this->prepareLinkedDataObjectCallback, array(&$do, $key));
}
if (!is_array($rLinks = $do->links())) {
$rLinks = array();
}
$rPk = $this->_getPrimaryKey($do);
//$rFields = $do->table();
list($lTable, $lField) = explode(':', $rLinks[$this->reverseLinks[$key]['field']]);
$formValues[$elName] = array();
if ($this->reverseLinks[$key]['collapse']) {
$table = $rowNames = array();
}
/*
if (isset($this->linkElementTypes[$elName])
&& $this->linkElementTypes[$elName] == 'subForm') {
// Do this to find only reverseLinks with the correct foreign key.
$do->{$this->reverseLinks[$key]['field']} = $this->_do->{$this->_getPrimaryKey($this->_do)};
}
*/
if ($do->find()) {
while ($do->fetch()) {
$label = $this->getDataObjectString($do);
if ($do->{$this->reverseLinks[$key]['field']} == $this->_do->$lField) {
$formValues[$elName][$do->$rPk] = $do->$rPk;
} elseif ($rLinked =& $do->getLink($this->reverseLinks[$key]['field'])) {
$label .= '<b>'.$this->reverseLinks[$key]['linkText'].$this->getDataObjectString($rLinked).'</b>';
}
if (isset($this->linkElementTypes[$elName])
&& $this->linkElementTypes[$elName] == 'subForm') {
unset($subFB, $subForm, $subFormEl);
$subFB =& DB_DataObject_FormBuilder::create($do,
false,
'QuickForm',
get_class($this));
$this->reverseLinks[$key]['FBs'][] =& $subFB;
$subFB->elementNamePrefix = $elName;
$subFB->elementNamePostfix = '_'.count($this->reverseLinks[$key]['FBs']);
$subFB->createSubmit = false;
$subFB->formHeaderText = $this->getDataObjectString($do);//$this->getFieldLabel($elName).' '.count($this->reverseLinks[$key]['FBs']);
$do->fb_linkNewValue = false;
$subForm =& $subFB->getForm();
$this->reverseLinks[$key]['SFs'][] = $subForm;
$subFormEl =& $this->_form->_createSubForm($elName.count($this->reverseLinks[$key]['FBs']), null, $subForm);
$element[] =& $subFormEl;
} else {
if ($this->reverseLinks[$key]['collapse']) {
$table[] = array($this->_form->_createCheckbox($elName.'['.$do->$rPk.']', '', $do->$rPk));
$rowNames[] = $label;
} else {
$element[] =& $this->_form->_createCheckbox($elName.'['.$do->$rPk.']', $label, $do->$rPk);
}
}
}
}
if (isset($this->reverseLinkNewValue[$elName]) && $this->reverseLinkNewValue[$elName] !== false) {
if (is_int($this->reverseLinkNewValue[$elName])) {
$totalSubforms = $this->reverseLinkNewValue[$elName];
} else {
$totalSubforms = 1;
}
for ($i = 0; $i < $totalSubforms; $i++) {
unset($subFB, $subForm, $subFormEl);
// Add a subform to add a new reverseLink record.
$do = DB_DataObject::factory($this->reverseLinks[$key]['table']);
$do->{$lField} = $this->_do->{$this->_getPrimaryKey($this->_do)};
$subFB =& DB_DataObject_FormBuilder::create($do,
false,
'QuickForm',
get_class($this));
$this->reverseLinks[$key]['FBs'][] =& $subFB;
$subFB->elementNamePrefix = $elName;
$subFB->elementNamePostfix = '_'.count($this->reverseLinks[$key]['FBs']);
$subFB->createSubmit = false;
$subFB->formHeaderText = 'New '.(isset($do->fb_formHeaderText)
? $do->fb_formHeaderText
: $this->prettyName($do->__table));
$do->fb_linkNewValue = false;
$subForm =& $subFB->getForm();
$this->reverseLinks[$key]['SFs'][] =& $subForm;
$subFormEl =& $this->_form->_createSubForm($elName.count($this->reverseLinks[$key]['FBs']), null, $subForm);
$element[] =& $subFormEl;
}
}
if ($this->reverseLinks[$key]['collapse']) {
$this->_form->_addElementGrid($elName, array(), $rowNames, $table);
$this->_form->_collapseRecordList($elName);
} else {
$this->_form->_addElementGroup($element, $elName, $this->crossLinkSeparator);
}
unset($element);
break;
case ($type & DB_DATAOBJECT_FORMBUILDER_GROUP):
unset($element);
$element =& $this->_form->_createHiddenField($key.'__placeholder');
break;
default:
if ($this->useAccessors
&& method_exists($this->_do, 'get' . $key)) {
$formValues[$key] = $this->_do->{'get'.$key}();
} else {
$formValues[$key] = $this->_do->$key;
}
if (!isset($element)) {
$element =& $this->_form->_createTextField($key);
}
} // End switch
//} // End else
if ($elValidator !== false) {
if (!isset($rules[$key])) {
$rules[$key] = array();
}
$rules[$key][] = array('validator' => $elValidator,
'rule' => $elValidRule,
'message' => $this->ruleViolationMessage);
} // End if
} // End else
//GROUP OR ELEMENT ADDITION
if (isset($this->preDefGroups[$key]) && !($type & DB_DATAOBJECT_FORMBUILDER_GROUP)) {
$group = $this->preDefGroups[$key];
$groups[$group][] = $element;
} elseif (isset($element)) {
if (is_array($element)) {
$this->_form->_addElementGroup($element, $key);
} else {
$this->_form->_addElement($element);
}
} // End if
//SET AUTO-RULES IF NOT DEACTIVATED FOR THIS OR ALL ELEMENTS
if (!$this->_excludeAllFromAutoRules
&& !in_array($key, $this->excludeFromAutoRules)) {
//ADD REQURED RULE FOR NOT_NULL FIELDS
if ((!in_array($key, $keys)
|| $this->hidePrimaryKey == false)
&& ($type & DB_DATAOBJECT_NOTNULL)
&& !in_array($key, $elements_to_freeze)
&& !($type & DB_DATAOBJECT_BOOL)) {
$this->_form->_setFormElementRequired($key);
$this->debug('Adding required rule for '.$key);
}
// VALIDATION RULES
if (isset($rules[$key])) {
$this->_form->_addFieldRules($rules[$key], $key);
if (is_array($rules[$key])) {
$msg = var_export($rules[$key], true);
} else {
$msg = $rules[$key];
}
$this->debug("Adding rule '$msg' to $key");
}
} else {
$this->debug($key.' excluded from auto-rules');
}
} // End foreach
if ($this->linkNewValue) {
$this->_form->_addRuleForLinkNewValues();
}
// Freeze fields that are not to be edited by the user
$this->_form->_freezeFormElements($elements_to_freeze);
//GROUP SUBMIT
$flag = true;
if (isset($this->preDefGroups['__submit__'])) {
$group = $this->preDefGroups['__submit__'];
if (count($groups[$group]) > 1) {
$groups[$group][] =& $this->_form->_createSubmitButton('__submit__', $this->submitText);
$flag = false;
} else {
$flag = true;
}
}
//GROUPING
if (isset($groups) && is_array($groups)) { //apply grouping
reset($groups);
while (list($grp, $elements) = each($groups)) {
if (count($elements) == 1) {
$this->_form->_addElement($elements[0]);
$this->_form->_moveElementBefore($this->_form->_getElementName($elements[0]), $grp.'__placeholder');
} elseif (count($elements) > 1) {
$this->_form->_addElementGroup($elements, $grp, ' ');
$this->_form->_moveElementBefore($grp, $grp.'__placeholder');
}
}
}
//ELEMENT SUBMIT
if ($flag == true && $this->createSubmit == true) {
$this->_form->_addSubmitButton('__submit__', $this->submitText);
}
$this->_form->_finishForm();
// Assign default values to the form
$fixedFormValues = array();
foreach ($formValues as $key => $value) {
$fixedFormValues[$this->getFieldName($key)] = $value;
}
$this->_form->_setFormDefaults($fixedFormValues);
return $this->_form->getForm();
}
/**
* Gets the name of the field to use in the form.
*
* @param string field's name
* @return string field name to use with form
*/
function getFieldName($fieldName) {
if (($pos = strpos($fieldName, '[')) !== false) {
$fieldName = substr($fieldName, 0, $pos).$this->elementNamePostfix.substr($fieldName, $pos);
} else {
$fieldName .= $this->elementNamePostfix;
}
return $this->elementNamePrefix.$fieldName;
}
/**
* DB_DataObject_FormBuilder::_explodeArrString()
*
* Internal method, will convert string representations of arrays as used in .ini files
* to real arrays. String format example:
* key1:value1,key2:value2,key3:value3,...
*
* @param string $str The string to convert to an array
* @access protected
* @return array
*/
function _explodeArrString($str) {
$ret = array();
$arr = explode(',', $str);
foreach ($arr as $mapping) {
if (strstr($mapping, ':')) {
$map = explode(':', $mapping);
$ret[$map[0]] = $map[1];
} else {
$ret[] = $mapping;
}
}
return $ret;
}
/**
* DB_DataObject_FormBuilder::_reorderElements()
*
* Changes the order in which elements are being processed, so that
* you can use QuickForm's default renderer or dynamic templates without
* being dependent on the field order in the database.
*
* Make a class property named "fb_preDefOrder" in your DataObject-derived classes
* which contains an array with the correct element order to use this feature.
*
* @return array Array in correct order or same as _getFieldsToRender if preDefOrder is not set
* @access protected
* @author Fabien Franzen <atelierfabien@home.nl>
*/
function _reorderElements() {
$elements = $this->_getFieldsToRender();
if ($this->preDefOrder) {
$this->debug('<br/>...reordering elements...<br/>');
$table = $this->_do->table();
$ordered = array();
foreach ($this->preDefOrder as $elem) {
if (isset($elements[$elem])) {
$ordered[$elem] = $elements[$elem]; //key=>type
if (isset($this->preDefGroups[$elem])
&& !in_array($this->preDefGroups[$elem], $ordered)
&& !in_array($this->preDefGroups[$elem], $this->preDefOrder)) {
$ordered[$this->preDefGroups[$elem]] = DB_DATAOBJECT_FORMBUILDER_GROUP;
}
} elseif (!isset($table[$elem])) {
$this->debug('<br/>...reorder not supported for invalid element(key) "'.$elem.'"...<br/>');
//return false;
}
}
$ordered = array_merge($ordered, array_diff_assoc($elements, $ordered));
return $ordered;
} else {
$this->debug('<br/>...reorder not supported, fb_preDefOrder is not set or is not an array, returning _getFieldsToRender...<br/>');
return $elements;
}
}
/**
* Returns an array of crosslink and triplelink elements for use the same as
* DB_DataObject::table().
*
* @return array the key is the name of the cross/triplelink element, the value
* is the type
*/
function _getSpecialElementNames() {
$ret = array();
foreach ($this->tripleLinks as $tripleLink) {
$ret[$this->_sanitizeFieldname('__tripleLink_'.$tripleLink['table'].
'_'.$tripleLink['fromField'].
'_'.$tripleLink['toField1'].
'_'.$tripleLink['toField2'])]
= DB_DATAOBJECT_FORMBUILDER_TRIPLELINK;
}
foreach ($this->crossLinks as $crossLink) {
$ret[$this->_sanitizeFieldName('__crossLink_'.$crossLink['table'].
'_'.$crossLink['fromField'].
'_'.$crossLink['toField'])]
= DB_DATAOBJECT_FORMBUILDER_CROSSLINK;
}
foreach ($this->reverseLinks as $reverseLink) {
$ret[$this->_sanitizeFieldName('__reverseLink_'.$reverseLink['table'].
'_'.$reverseLink['field'])]
= DB_DATAOBJECT_FORMBUILDER_REVERSELINK;
}
foreach ($this->preDefGroups as $group) {
$ret[$group] = DB_DATAOBJECT_FORMBUILDER_GROUP;
}
return $ret;
}
/**
* DB_DataObject_FormBuilder::useForm()
*
* Sometimes, it might come in handy not just to create a new QuickForm object,
* but to work with an existing one. Using FormBuilder together with
* HTML_QuickForm_Controller or HTML_QuickForm_Page is such an example ;-)
* If you do not call this method before the form is generated, a new QuickForm
* object will be created (default behaviour).
*
* @param $form object A HTML_QuickForm object (or extended from that)
* @param $append boolean If TRUE, the form will be appended to the one generated by FormBuilder. If false, FormBuilder will just add its own elements to this form.
* @return boolean Returns false if the passed object was not a HTML_QuickForm object or a QuickForm object was already created
* @access public
*/
function useForm(&$form, $append = false)
{
return $this->_form->useForm($form, $append);
}
/**
* DB_DataObject_FormBuilder::getFieldLabel()
*
* Returns the label for the given field name. If no label is specified,
* the fieldname will be returned with ucfirst() applied.
*
* @param $fieldName string The field name
* @return string
* @access public
*/
function getFieldLabel($fieldName)
{
if (isset($this->fieldLabels[$fieldName])) {
return $this->fieldLabels[$fieldName];
}
return $this->prettyName($fieldName);
}
/**
* DB_DataObject_FormBuilder::prettyName()
*
* "Pretties" a name.
* 'fieldName' => 'Field Name'
* 'TABLE_NAME' => 'Table Name'
*
* @param $name string The name to "pretty"
* @return string
* @access public
*/
function prettyName($name) {
return ucwords(strtolower(preg_replace('/[^a-z0-9]/Si', ' ', preg_replace('/([a-z0-9])([A-Z])/S', '\1 \2', $name))));
}
/**
* DB_DataObject_FormBuilder::getDataObjectString()
*
* Returns a string which identitfies this dataobject.
* If multiple display fields are given, will display them all seperated by ", ".
* If a display field is a foreign key (link) the display value for the record it
* points to will be used as long as the linkDisplayLevel has not been reached.
* Its display value will be surrounded by parenthesis as it may have multiple
* display fields of its own.
*
* May be called statically.
*
* Will use display field configurations from these locations, in this order:
* 1) $displayFields parameter
* 2) the fb_linkDisplayFields member variable of the dataobject
* 3) the linkDisplayFields member variable of this class (if not called statically)
* 4) all fields returned by the DO's table() function
*
* @param DB_DataObject the dataobject to get the display value for, must be populated
* @param mixed field to use to display, may be an array with field names or a single field.
* Will only be used for this DO, not linked DOs. If you wish to set the display fields
* all DOs the same, set the option in the FormBuilder class instance.
* @param int the maximum link display level. If null, $this->linkDisplayLebel will be used
* if it exists, otherwise 3 will be used. {@see DB_DataObject_FormBuilder::linkDisplayLevel}
* @param int the current recursion level. For internal use only.
* @return string select display value for this field
* @access public
*/
function getDataObjectString(&$do, $displayFields = false, $linkDisplayLevel = null, $level = 1) {
if ($linkDisplayLevel === null) {
$linkDisplayLevel = (isset($this) && isset($this->linkDisplayLevel)) ? $this->linkDisplayLevel : 3;
}
if (!is_array($links = $do->links())) {
$links = array();
}
if ($displayFields === false) {
if (isset($do->fb_linkDisplayFields)) {
$displayFields = $do->fb_linkDisplayFields;
} elseif (isset($this) && isset($this->linkDisplayFields) && $this->linkDisplayFields) {
$displayFields = $this->linkDisplayFields;
}
if (!$displayFields) {
$displayFields = array_keys($do->table());
}
}
$ret = '';
$first = true;
foreach ($displayFields as $field) {
if ($first) {
$first = false;
} else {
$ret .= ', ';
}
if (isset($do->$field)) {
if ($linkDisplayLevel > $level && isset($links[$field])) {
/*list ($linkTable, $linkField) = $links[$field];
$subDo = DB_DataObject::factory($linkTable);
$subDo->$linkField = $do->$field;*/
if ($subDo = $do->getLink($field)) {
if (isset($this) && is_a($this, 'DB_DataObject_FormBuilder')) {
$ret .= '('.$this->getDataObjectString($subDo, false, $linkDisplayLevel, $level + 1).')';
} else {
$ret .= '('.DB_DataObject_FormBuilder::getDataObjectString($subDo, false, $linkDisplayLevel, $level + 1).')';
}
continue;
}
}
$ret .= $do->$field;
}
}
return $ret;
}
/**
* DB_DataObject_FormBuilder::getSelectOptions()
*
* Returns an array of options for use with the HTML_QuickForm "select" element.
* It will try to fetch all related objects (if any) for the given field name and
* build the array.
* For the display name of the option, it will try to use
* the settings in the database.formBuilder.ini file. If those are not found,
* the linked object's property "fb_linkDisplayFields". If that one is not present,
* it will try to use the global configuration setting "linkDisplayFields".
* Can also be called with a second parameter containing the name of the display
* field - this will override all other settings.
* Same goes for "linkOrderFields", which determines the field name used for
* sorting the option elements. If neither a config setting nor a class property
* of that name is set, the display field name will be used.
*
* @param string $field The field to fetch the links from. You should make sure the field actually *has* links before calling this function (see: DB_DataObject::links())
* @param string $displayFields (Optional) The name of the field used for the display text of the options
* @param bool $selectAddEmpty (Optional) If true, an empty option will be added to the list of options
* If false, the selectAddEmpty member var will be checked
* @param string $emptyLabel (Optional) Label to use for empty options (defaults to $this->selectAddEmptyLabel)
*
* @return array strings representing all of the records in the table $field links to.
* @access public
*/
function getSelectOptions($field, $displayFields = false, $selectAddEmpty = false, $emptyLabel = false)
{
if (empty($this->_do->_database)) {
// TEMPORARY WORKAROUND !!! Guarantees that DataObject config has
// been loaded and all link information is available.
$this->_do->keys();
}
if (!is_array($links = $this->_do->links())) {
$links = array();
}
$link = explode(':', $links[$field]);
$res = $this->_getSelectOptions($link[0],
$displayFields,
$selectAddEmpty || in_array($field, $this->selectAddEmpty),
$field,
$link[1],
$emptyLabel);
return $res;
}
/**
* Internal function to get the select options for a table.
*
* @param string The table to get the select display strings for.
* @param array array of diaply fields to use. Will default to the FB or DO options.
* @param bool If set to true, there will be an empty option in the returned array.
* @param string the field in the current table which we're getting options for
* @param string the field to use for the value of the options. Defaults to the PK of the $table
* @param string label to use for an empty option (defaults to $this->selectAddEmptyLabel)
*
* @return array strings representing all of the records in $table.
* @access protected
*/
function _getSelectOptions($table,
$displayFields = false,
$selectAddEmpty = false,
$field = false,
$valueField = false,
$emptyLabel = false) {
$opts = DB_DataObject::factory($table);
if (is_a($opts, 'db_dataobject')) {
if ($this->isCallableAndExists($this->prepareLinkedDataObjectCallback)) {
call_user_func_array($this->prepareLinkedDataObjectCallback, array(&$opts, $field));
}
if ($valueField === false) {
$valueField = $this->_getPrimaryKey($opts);
}
if (strlen($valueField)) {
if ($displayFields === false) {
if (isset($opts->fb_linkDisplayFields)) {
$displayFields = $opts->fb_linkDisplayFields;
} elseif ($this->linkDisplayFields){
$displayFields = $this->linkDisplayFields;
} else {
$displayFields = array($valueField);
}
}
if (isset($opts->fb_linkOrderFields)) {
$orderFields = $opts->fb_linkOrderFields;
} elseif ($this->linkOrderFields){
$orderFields = $this->linkOrderFields;
} else {
$orderFields = $displayFields;
}
$orderStr = '';
$first = true;
foreach ($orderFields as $col) {
if ($first) {
$first = false;
} else {
$orderStr .= ', ';
}
$orderStr .= $col;
}
if ($orderStr) {
$opts->orderBy($orderStr);
}
$list = array();
// FIXME!
if ($selectAddEmpty) {
$list[''] = $emptyLabel !== false ? $emptyLabel : $this->selectAddEmptyLabel;
}
// FINALLY, let's see if there are any results
if ($opts->find() > 0) {
while ($opts->fetch()) {
$list[$opts->$valueField] = $this->getDataObjectString($opts, $displayFields);
}
}
return $list;
} else {
return array();
}
}
$this->debug('Error: '.get_class($opts).' does not inherit from DB_DataObject');
return array();
}
/**
* DB_DataObject_FormBuilder::populateOptions()
*
* Populates public member vars with fb_ equivalents in the DataObject.
*/
function populateOptions() {
$badVars = array('linkDisplayFields', 'linkOrderFields');
foreach (get_object_vars($this) as $var => $value) {
if ($var[0] != '_' && !in_array($var, $badVars) && isset($this->_do->{'fb_'.$var})) {
$this->$var = $this->_do->{'fb_'.$var};
}
}
foreach ($this->crossLinks as $key => $crossLink) {
if (!isset($crossLink['type'])) {
$crossLink['type'] = 'radio';
}
if (!isset($crossLink['collapse'])) {
$crossLink['collapse'] = false;
}
unset($do);
$do = DB_DataObject::factory($crossLink['table']);
if (PEAR::isError($do)) {
return PEAR::raiseError('Cannot load dataobject for table '.$crossLink['table'].' - '.$do->getMessage());
}
if (!is_array($links = $do->links())) {
$links = array();
}
if (isset($crossLink['fromField'])) {
$fromField = $crossLink['fromField'];
} else {
unset($fromField);
}
if (isset($crossLink['toField'])) {
$toField = $crossLink['toField'];
} else {
unset($toField);
}
if (!isset($toField) || !isset($fromField)) {
foreach ($links as $field => $link) {
list($linkTable, $linkField) = explode(':', $link);
if (!isset($fromField) && $linkTable == $this->_do->__table) {
$fromField = $field;
} elseif (!isset($toField) && (!isset($fromField) || $linkField != $fromField)) {
$toField = $field;
}
}
}
unset($this->crossLinks[$key]);
$groupName = $this->_sanitizeFieldName('__crossLink_'.$crossLink['table'].
'_'.$fromField.
'_'.$toField);
$this->crossLinks[$groupName] = array_merge($crossLink,
array('fromField' => $fromField,
'toField' => $toField));
foreach (array('preDefOrder', 'fieldsToRender', 'userEditableFields') as $arrName) {
foreach ($this->{$arrName} as $key => $value) {
if ($this->_sanitizeFieldName($value)
== $this->_sanitizeFieldName('__crossLink_'.$crossLink['table'])) {
$this->{$arrName}[$key] = $groupName;
}
}
}
foreach (array('preDefElements', 'fieldLabels', 'fieldAttributes') as $arrName) {
if (isset($this->{$arrName}[$this->_sanitizeFieldName('__crossLink_'.$crossLink['table'])])) {
if (!isset($this->{$arrName}[$groupName])) {
$this->{$arrName}[$groupName] =& $this->{$arrName}['__crossLink_'.$crossLink['table']];
}
unset($this->{$arrName}[$this->_sanitizeFieldName('__crossLink_'.$crossLink['table'])]);
}
}
}
foreach ($this->tripleLinks as $key => $tripleLink) {
//$freeze = array_search($elName, $elements_to_freeze);
unset($do);
$do = DB_DataObject::factory($tripleLink['table']);
if (PEAR::isError($do)) {
die($do->getMessage());
}
if (!is_array($links = $do->links())) {
$links = array();
}
if (isset($tripleLink['fromField'])) {
$fromField = $tripleLink['fromField'];
} else {
unset($fromField);
}
if (isset($tripleLink['toField1'])) {
$toField1 = $tripleLink['toField1'];
} else {
unset($toField1);
}
if (isset($tripleLink['toField2'])) {
$toField2 = $tripleLink['toField2'];
} else {
unset($toField2);
}
if (!isset($toField2) || !isset($toField1) || !isset($fromField)) {
foreach ($links as $field => $link) {
list($linkTable, $linkField) = explode(':', $link);
if (!isset($fromField) && $linkTable == $this->_do->__table) {
$fromField = $field;
} elseif (!isset($toField1) && (!isset($fromField) || $linkField != $fromField)) {
$toField1 = $field;
} elseif (!isset($toField2) && (!isset($fromField) || $linkField != $fromField) && $linkField != $toField1) {
$toField2 = $field;
}
}
}
unset($this->tripleLinks[$key]);
$elName = $this->_sanitizeFieldName('__tripleLink_' . $tripleLink['table'].
'_'.$fromField.
'_'.$toField1.
'_'.$toField2);
$this->tripleLinks[$elName] = array_merge($tripleLink,
array('fromField' => $fromField,
'toField1' => $toField1,
'toField2' => $toField2));
foreach (array('preDefOrder', 'fieldsToRender', 'userEditableFields') as $arrName) {
foreach ($this->{$arrName} as $key => $value) {
if ($this->_sanitizeFieldName($value)
== $this->_sanitizeFieldName('__tripleLink_'.$tripleLink['table'])) {
$this->{$arrName}[$key] = $elName;
}
}
}
foreach (array('preDefElements', 'fieldLabels', 'fieldAttributes') as $arrName) {
if (isset($this->{$arrName}[$this->_sanitizeFieldName('__tripleLink_'.$tripleLink['table'])])) {
if (!isset($this->{$arrName}[$elName])) {
$this->{$arrName}[$elName] =& $this->{$arrName}[$this->_sanitizeFieldName('__tripleLink_'.$tripleLink['table'])];
}
unset($this->{$arrName}[$this->_sanitizeFieldName('__tripleLink_'.$tripleLink['table'])]);
}
}
}
foreach ($this->reverseLinks as $key => $reverseLink) {
if (!isset($reverseLink['field'])) {
unset($do);
$do = DB_DataObject::factory($reverseLink['table']);
if (!is_array($links = $do->links())) {
$links = array();
}
foreach ($links as $field => $link) {
list($linkTable, $linkField) = explode(':', $link);
if ($linkTable == $this->_do->__table) {
$reverseLink['field'] = $field;
break;
}
}
}
$elName = $this->_sanitizeFieldName('__reverseLink_'.$reverseLink['table'].
'_'.$reverseLink['field']);
if (!isset($reverseLink['linkText'])) {
$reverseLink['linkText'] = ' - currently linked to - ';
}
if (!isset($reverseLink['collapse'])) {
$reverseLink['collapse'] = false;
}
unset($this->reverseLinks[$key]);
$this->reverseLinks[$elName] = $reverseLink;
foreach (array('preDefOrder', 'fieldsToRender', 'userEditableFields') as $arrName) {
foreach ($this->{$arrName} as $key => $value) {
if ($this->_sanitizeFieldName($value)
== $this->_sanitizeFieldName('__reverseLink_'.$reverseLink['table'])) {
$this->{$arrName}[$key] = $elName;
}
}
}
foreach (array('preDefElements', 'fieldLabels', 'fieldAttributes', 'reverseLinkNewValue') as $arrName) {
if (isset($this->{$arrName}[$this->_sanitizeFieldName('__reverseLink_'.$reverseLink['table'])])) {
if (!isset($this->{$arrName}[$elName])) {
$this->{$arrName}[$elName] =& $this->{$arrName}[$this->_sanitizeFieldName('__reverseLink_'.$reverseLink['table'])];
}
unset($this->{$arrName}[$this->_sanitizeFieldName('__reverseLink_'.$reverseLink['table'])]);
}
}
}
if (is_array($this->linkNewValue)) {
$newArr = array();
foreach ($this->linkNewValue as $field) {
$newArr[$field] = $field;
}
$this->linkNewValue = $newArr;
} else {
if ($this->linkNewValue) {
$this->linkNewValue = array();
if (is_array($links = $this->_do->links())) {
foreach ($links as $link => $to) {
$this->linkNewValue[$link] = $link;
}
}
} else {
$this->linkNewValue = array();
}
}
if (!is_array($this->reverseLinkNewValue)) {
if ($new = $this->reverseLinkNewValue) {
$this->reverseLinkNewValue = array();
if (is_array($this->reverseLinks)) {
foreach ($this->reverseLinks as $key => $reverseLink) {
$this->reverseLinkNewValue['__reverseLink_'.$reverseLink['table'].'_'.$reverseLink['field']] = $new;
}
}
} else {
$this->reverseLinkNewValue = array();
}
}
if (is_array($this->excludeFromAutoRules)
&& in_array('__ALL__', $this->excludeFromAutoRules)
|| '__ALL__' == $this->excludeFromAutoRules) {
$this->excludeFromAutoRules = array();
$this->_excludeAllFromAutoRules = true;
}
$this->_form->populateOptions();
}
/**
* DB_DataObject_FormBuilder::getForm()
*
* Returns a HTML form that was automagically created by _generateForm().
* You need to use the get() method before calling this one in order to
* prefill the form with the retrieved data.
*
* If you have a method named "preGenerateForm()" in your DataObject-derived class,
* it will be called before _generateForm(). This way, you can create your own elements
* there and add them to the "fb_preDefElements" property, so they will not be auto-generated.
*
* If you have your own "getForm()" method in your class, it will be called <b>instead</b> of
* _generateForm(). This enables you to have some classes that make their own forms completely
* from scratch, without any auto-generation. Use this for highly complex forms. Your getForm()
* method needs to return the complete HTML_QuickForm object by reference.
*
* If you have a method named "postGenerateForm()" in your DataObject-derived class, it will
* be called after _generateForm(). This allows you to remove some elements that have been
* auto-generated from table fields but that you don't want in the form.
*
* Many ways lead to rome.
*
* @param string $action The form action. Optional. If set to false (default), $_SERVER['PHP_SELF'] is used.
* @param string $target The window target of the form. Optional. Defaults to '_self'.
* @param string $formName The name of the form, will be used in "id" and "name" attributes.
* If set to false (default), the class name is used, prefixed with "frm"
* @param string $method The submit method. Defaults to 'post'.
* @return object
* @access public
*/
function &getForm($action = false, $target = '_self', $formName = false, $method = 'post')
{
if ($this->isCallableAndExists($this->preGenerateFormCallback)) {
call_user_func_array($this->preGenerateFormCallback, array(&$this));
}
$this->populateOptions();
if (method_exists($this->_do, 'getform')) {
if ($this->useCallTimePassByReference) {
eval('$obj = $this->_do->getForm($action, $target, $formName, $method, &$this);');
} else {
$obj = $this->_do->getForm($action, $target, $formName, $method, $this);
}
} else {
$obj =& $this->_generateForm($action, $target, $formName, $method);
}
if ($this->isCallableAndExists($this->postGenerateFormCallback)) {
call_user_func_array($this->postGenerateFormCallback, array(&$obj, &$this));
}
return $obj;
}
/**
* DB_DataObject_FormBuilder::_date2array()
*
* Takes a string representing a date or a unix timestamp and turns it into an
* array suitable for use with the QuickForm data element.
* When using a string, make sure the format can be handled by the PEAR::Date constructor!
*
* Beware: For the date conversion to work, you must at least use the letters "d", "m" and "Y" in
* your format string (see "dateElementFormat" option). If you want to enter a time as well,
* you will have to use "H", "i" and "s" as well. Other letters will not work! Exception: You can
* also use "M" instead of "m" if you want plain text month names.
*
* @param mixed $date A unix timestamp or the string representation of a date, compatible to strtotime()
* @return array
* @access protected
*/
function _date2array($date)
{
$da = array();
if (is_string($date)) {
if (preg_match('/^\d+:\d+(:\d+|)(\s+[ap]m|)$/i', $date)) {
$date = date('Y-m-d ').$date;
$getDate = false;
} else {
$getDate = true;
}
include_once('Date.php');
$dObj = new Date($date);
if ($getDate) {
$da['d'] = $dObj->getDay();
$da['l'] = $da['D'] = $dObj->getDayOfWeek();
$da['m'] = $da['M'] = $da['F'] = $dObj->getMonth();
$da['Y'] = $da['y'] = $dObj->getYear();
}
$da['H'] = $dObj->getHour();
$da['h'] = $da['H'] % 12;
if ($da['h'] == 0) {
$da['h'] = 12;
}
$da['g'] = $da['h'];
$da['i'] = $dObj->getMinute();
$da['s'] = $dObj->getSecond();
if ($da['H'] >= 12) {
$da['a'] = 'pm';
$da['A'] = 'PM';
} else {
$da['a'] = 'am';
$da['A'] = 'AM';
}
unset($dObj);
} else {
if (is_int($date)) {
$time = $date;
} else {
$time = time();
}
$da['d'] = date('d', $time);
$da['l'] = $da['D'] = date('w', $time);
$da['m'] = $da['M'] = $da['F'] = date('m', $time);
$da['Y'] = $da['y'] = date('Y', $time);
$da['H'] = date('H', $time);
$da['g'] = date('g', $time);
$da['h'] = date('h', $time);
$da['i'] = date('i', $time);
$da['s'] = date('s', $time);
$da['a'] = date('a', $time);
$da['A'] = date('A', $time);
}
DB_DataObject_FormBuilder::debug('<i>_date2array():</i> from '.$date.' to '.serialize($da).' ...');
return $da;
}
/**
* DB_DataObject_FormBuilder::_array2date()
*
* Takes a date array as used by the QuickForm date element and turns it back into
* a string representation suitable for use with a database date field (format 'YYYY-MM-DD').
* If second parameter is true, it will return a unix timestamp instead. //FRANK: Not at this point it wont
*
* Beware: For the date conversion to work, you must at least use the letters "d", "m" and "Y" in
* your format string (see "dateElementFormat" option). If you want to enter a time as well,
* you will have to use "H", "i" and "s" as well. Other letters will not work! Exception: You can
* also use "M" instead of "m" if you want plain text month names.
*
* @param array $date An array representation of a date, as user in HTML_QuickForm's date element
* @param boolean $timestamp Optional. If true, return a timestamp instead of a string. Defaults to false.
* @return mixed
* @access protected
*/
function _array2date($dateInput, $timestamp = false)
{
if (isset($dateInput['M'])) {
$month = $dateInput['M'];
} elseif (isset($dateInput['m'])) {
$month = $dateInput['m'];
} elseif (isset($dateInput['F'])) {
$month = $dateInput['F'];
}
if (isset($dateInput['Y'])) {
$year = $dateInput['Y'];
} elseif (isset($dateInput['y'])) {
$year = $dateInput['y'];
}
if (isset($dateInput['H'])) {
$hour = $dateInput['H'];
} elseif (isset($dateInput['h']) || isset($dateInput['g'])) {
if (isset($dateInput['h'])) {
$hour = $dateInput['h'];
} elseif (isset($dateInput['g'])) {
$hour = $dateInput['g'];
}
if (isset($dateInput['a'])) {
$ampm = $dateInput['a'];
} elseif (isset($dateInput['A'])) {
$ampm = $dateInput['A'];
}
if (isset($ampm) && strtolower(preg_replace('/[\.\s,]/', '', $ampm)) == 'pm') {
if ($hour != '12') {
$hour += 12;
if ($hour == 24) {
$hour = '';
++$dateInput['d'];
}
}
} else {
if ($hour == '12') {
$hour = '00';
}
}
}
$strDate = '';
if (isset($year) || isset($month) || isset($dateInput['d'])) {
if (isset($year) && ($len = strlen($year)) > 0) {
if ($len < 2) {
$year = '0'.$year;
}
if ($len < 4) {
$year = substr(date('Y'), 0, 2).$year;
}
} else {
$year = '0000';
}
if(isset($month) && ($len = strlen($month)) > 0) {
if ($len < 2) {
$month = '0'.$month;
}
} else {
$month = '00';
}
if (isset($dateInput['d']) && ($len = strlen($dateInput['d'])) > 0) {
if ($len < 2) {
$dateInput['d'] = '0'.$dateInput['d'];
}
} else {
$dateInput['d'] = '00';
}
$strDate .= $year.'-'.$month.'-'.$dateInput['d'];
}
if (isset($hour) || isset($dateInput['i']) || isset($dateInput['s'])) {
if (isset($hour) && ($len = strlen($hour)) > 0) {
if ($len < 2) {
$hour = '0'.$hour;
}
} else {
$hour = '00';
}
if (isset($dateInput['i']) && ($len = strlen($dateInput['i'])) > 0) {
if ($len < 2) {
$dateInput['i'] = '0'.$dateInput['i'];
}
} else {
$dateInput['i'] = '00';
}
if (!empty($strDate)) {
$strDate .= ' ';
}
$strDate .= $hour.':'.$dateInput['i'];
if (isset($dateInput['s']) && ($len = strlen($dateInput['s'])) > 0) {
$strDate .= ':'.($len < 2 ? '0' : '').$dateInput['s'];
}
}
DB_DataObject_FormBuilder::debug('<i>_array2date():</i>'.serialize($dateInput).' to '.$strDate.' ...');
return $strDate;
}
/**
* DB_DataObject_FormBuilder::validateData()
*
* Makes a call to the current DataObject's validate() method and returns the result.
*
* @return mixed
* @access public
* @see DB_DataObject::validate()
*/
function validateData()
{
$this->_validationErrors = $this->_do->validate();
return $this->_validationErrors;
}
/**
* DB_DataObject_FormBuilder::getValidationErrors()
*
* Returns errors from data validation. If errors have occured, this will be
* an array with the fields that have errors, otherwise a boolean.
*
* @return mixed
* @access public
* @see DB_DataObject::validate()
*/
function getValidationErrors()
{
return $this->_validationErrors;
}
/**
* Convenience function to add a DataObject's last error's message to the error message
*
* @param string error message
* @param DB_DataObject DataObject to check for error in
* @return PEAR_Error The error object
*/
function &_raiseDoError($message, &$do) {
if (PEAR::isError($do->_lastError)) {
$message .= ' - Error from DataObject: '.$do->_lastError->getMessage().' '.$do->_lastError->getUserInfo();
}
return PEAR::raiseError($message, null, null, null, $do);
}
/**
* DB_DataObject_FormBuilder::processForm()
*
* This will take the submitted form data and put it back into the object's properties.
* If the primary key is not set or NULL, it will be assumed that you wish to insert a new
* element into the database, so DataObject's insert() method is invoked.
* Otherwise, an update() will be performed.
* <i><b>Careful:</b> If you're using natural keys or cross-referencing tables where you don't have
* one dedicated primary key, this will always assume that you want to do an update! As there
* won't be a matching entry in the table, no action will be performed at all - the reason
* for this behaviour can be very hard to detect. Thus, if you have such a situation in one
* of your tables, simply override this method so that instead of the key check it will try
* to do a SELECT on the table using the current settings. If a match is found, do an update.
* If not, do an insert.</i>
* This method is perfect for use with QuickForm's process method. Example:
* <code>
* if ($form->validate()) {
* $form->freeze();
* $form->process(array(&$formGenerator,'processForm'), false);
* }
* </code>
*
* If you wish to enforce a special type of query, use the forceQueryType() method.
*
* Always remember to pass your objects by reference - otherwise, if the operation was
* an insert, the primary key won't get updated with the new database ID because processForm()
* was using a local copy of the object!
*
* If a method named "preProcessForm()" exists in your derived class, it will be called before
* processForm() starts doing its magic. The data that has been submitted by the form
* will be passed to that method as a parameter.
* Same goes for a method named "postProcessForm()", with the only difference - you might
* have guessed this by now - that it's called after the insert/update operations have
* been done. Use this for filtering data, notifying users of changes etc.pp. ...
*
* @param array $values The values of the submitted form
* @return mixed TRUE if database operations were performed, FALSE if not, PEAR_Error on error
* @access public
*/
function processForm($values)
{
$origDo = clone($this->_do);
if ($this->elementNamePrefix !== '' || $this->elementNamePostfix !== '') {
$origValues = $values;
$values = $this->_getMyValues($values);
}
$this->debug('<br>...processing form data...<br>');
if ($this->isCallableAndExists($this->preProcessFormCallback)) {
call_user_func_array($this->preProcessFormCallback, array(&$values, &$this));
}
$editableFields = array_intersect($this->_getUserEditableFields(),
array_keys($this->_getFieldsToRender()));
$tableFields = $this->_do->table();
if (!is_array($links = $this->_do->links())) {
$links = array();
}
foreach ($values as $field => $value) {
$this->debug('Field '.$field.' ');
// Double-check if the field may be edited by the user... if not, don't
// set the submitted value, it could have been faked!
if (in_array($field, $editableFields)) {
if (isset($tableFields[$field])) {
if (($tableFields[$field] & DB_DATAOBJECT_DATE) || in_array($field, $this->dateFields)) {
$this->debug('DATE CONVERSION for using callback from '.$value.' ...');
if ($this->isCallableAndExists($this->dateToDatabaseCallback)) {
$value = call_user_func($this->dateToDatabaseCallback, $value);
} else {
$this->debug('WARNING: dateToDatabaseCallback not callable', 'FormBuilder');
}
} elseif (($tableFields[$field] & DB_DATAOBJECT_TIME) || in_array($field, $this->timeFields)) {
$this->debug('TIME CONVERSION for using callback from '.$value.' ...');
if ($this->isCallableAndExists($this->dateToDatabaseCallback)) {
$value = call_user_func($this->dateToDatabaseCallback, $value);
} else {
$this->debug('WARNING: dateToDatabaseCallback not callable', 'FormBuilder');
}
} elseif (is_array($value)) {
if (isset($value['tmp_name'])) {
$this->debug(' (converting file array) ');
$value = $value['name'];
//JUSTIN
//This is not really a valid assumption IMHO. This should only be done if the type is
// date or the field is in dateFields
/*} else {
$this->debug("DATE CONVERSION using callback from $value ...");
$value = call_user_func($this->dateToDatabaseCallback, $value);*/
}
}
if (isset($links[$field])) {
if ($value == $this->linkNewValueText && $tableFields[$field] & DB_DATAOBJECT_INT) {
$value = 0;
} elseif ($value === '') {
$this->debug('Casting to NULL');
require_once('DB/DataObject/Cast.php');
$value = DB_DataObject_Cast::sql('NULL');
}
}
$this->debug('is substituted with "'.print_r($value, true).'".<br/>');
// See if a setter method exists in the DataObject - if so, use that one
if ($this->useMutators
&& method_exists($this->_do, 'set' . $field)) {
$this->_do->{'set'.$field}($value);
} else {
// Otherwise, just set the property 'normally'...
$this->_do->$field = $value;
}
} else {
$this->debug('is not a valid field.<br/>');
}
} else {
$this->debug('is defined not to be editable by the user!<br/>');
}
}
foreach ($this->booleanFields as $boolField) {
if (in_array($boolField, $editableFields)
&& !isset($values[$boolField])) {
if ($this->useMutators
&& method_exists($this->_do, 'set' . $boolField)) {
$this->_do->{'set'.$boolField}(0);
} else {
$this->_do->$boolField = 0;
}
}
}
foreach ($tableFields as $field => $type) {
if (($type & DB_DATAOBJECT_BOOL)
&& in_array($field, $editableFields)
&& !isset($values[$field])) {
if ($this->useMutators
&& method_exists($this->_do, 'set' . $field)) {
$this->_do->{'set'.$field}(0);
} else {
$this->_do->$field = 0;
}
}
}
$dbOperations = true;
if ($this->validateOnProcess === true) {
$this->debug('Validating data... ');
if (is_array($errors = $this->validateData())) {
$dbOperations = false;
}
}
$pk = $this->_getPrimaryKey($this->_do);
// Data is valid, let's store it!
if ($dbOperations) {
//take care of linkNewValues
/*if (isset($values['__DB_DataObject_FormBuilder_linkNewValue_'])) {
foreach ($values['__DB_DataObject_FormBuilder_linkNewValue_'] as $elName => $subTable) {*/
if (isset($this->_form->_linkNewValueForms)) {
foreach (array_keys($this->_form->_linkNewValueForms) as $elName) {
$subTable = $this->_form->_linkNewValueDOs[$elName]->tableName();
if (isset($values['__DB_DataObject_FormBuilder_linkNewValue__'.$elName])) {
if ($values[$elName] == $this->linkNewValueText) {
//$this->_form->_prepareForLinkNewValue($elName, $subTable);
$ret = $this->_form->_linkNewValueForms[$elName]->process(array(&$this->_form->_linkNewValueFBs[$elName], 'processForm'), false);
if (PEAR::isError($ret)) {
$this->debug('Error processing linkNewValue for '.serialize($this->_form->_linkNewValueDOs[$elName]));
return PEAR::raiseError('Error processing linkNewValue - Error from processForm: '.$ret->getMessage(),
null,
null,
null,
$this->_form->_linkNewValueDOs[$elName]);
}
$subPk = $this->_form->_linkNewValueFBs[$elName]->_getPrimaryKey($this->_form->_linkNewValueDOs[$elName]);
$this->_do->$elName = $values[$elName] = $this->_form->_linkNewValueDOs[$elName]->$subPk;
}
}
}
}
$action = $this->_queryType;
if ($this->_queryType == DB_DATAOBJECT_FORMBUILDER_QUERY_AUTODETECT) {
// Could the primary key be detected?
if ($pk === false) {
// Nope, so let's exit and return false. Sorry, you can't store data using
// processForm with this DataObject unless you do some tweaking :-(
$this->debug('Primary key not detected - storing data not possible.');
return false;
}
$action = DB_DATAOBJECT_FORMBUILDER_QUERY_FORCEUPDATE;
if (!isset($this->_do->$pk) || !strlen($this->_do->$pk)) {
$action = DB_DATAOBJECT_FORMBUILDER_QUERY_FORCEINSERT;
}
}
switch ($action) {
case DB_DATAOBJECT_FORMBUILDER_QUERY_FORCEINSERT:
if (false === ($id = $this->_do->insert())) {
$this->debug('Insert of main record failed');
return $this->_raiseDoError('Insert of main record failed', $this->_do);
}
$this->debug('ID ('.$pk.') of the new object: '.$id.'<br/>');
break;
case DB_DATAOBJECT_FORMBUILDER_QUERY_FORCEUPDATE:
if (false === $this->_do->update($origDo)) {
$this->debug('Update of main record failed');
return $this->_raiseDoError('Update of main record failed', $this->_do);
}
$this->debug('Object updated.<br/>');
break;
}
// process tripleLinks
foreach ($this->tripleLinks as $tripleLink) {
$tripleLinkName = $this->_sanitizeFieldName('__tripleLink_'.$tripleLink['table'].
'_'.$tripleLink['fromField'].
'_'.$tripleLink['toField1'].
'_'.$tripleLink['toField2']);
if (in_array($tripleLinkName, $editableFields)) {
unset($do);
$do = DB_DataObject::factory($tripleLink['table']);
$fromField = $tripleLink['fromField'];
$toField1 = $tripleLink['toField1'];
$toField2 = $tripleLink['toField2'];
if (isset($values[$tripleLinkName])) {
$rows = $values[$tripleLinkName];
} else {
$rows = array();
}
$links = $do->links();
list ($linkTable, $linkField) = explode(':', $links[$fromField]);
$do->$fromField = $this->_do->$linkField;
$do->selectAdd();
$do->selectAdd($toField1);
$do->selectAdd($toField2);
if ($doKey = $this->_getPrimaryKey($do)) {
$do->selectAdd($doKey);
}
if ($this->isCallableAndExists($this->prepareLinkedDataObjectCallback)) {
call_user_func_array($this->prepareLinkedDataObjectCallback, array(&$do, $tripleLinkName));
}
$oldFieldValues = array();
if ($do->find()) {
while ($do->fetch()) {
if (isset($rows[$do->$toField1]) && isset($rows[$do->$toField1][$do->$toField2])) {
$oldFieldValues[$do->$toField1][$do->$toField2] = true;
} else {
if (false === $do->delete()) {
$this->debug('Failed to delete tripleLink '.serialize($do));
return $this->_raiseDoError('Failed to delete tripleLink', $do);
}
}
}
}
if (count($rows) > 0) {
foreach ($rows as $rowid => $row) {
if (count($row) > 0) {
foreach ($row as $fieldvalue => $on) {
if (!isset($oldFieldValues[$rowid]) || !isset($oldFieldValues[$rowid][$fieldvalue])) {
unset($do);
$do = DB_DataObject::factory($tripleLink['table']);
$do->$fromField = $this->_do->$linkField;
$do->$toField1 = $rowid;
$do->$toField2 = $fieldvalue;
if (false === $do->insert()) {
$this->debug('Failed to insert tripleLink '.serialize($do));
return $this->_raiseDoError('Failed to insert tripleLink', $do);
}
}
}
}
}
}
}
}
//process crossLinks
foreach ($this->crossLinks as $crossLink) {
$crossLinkName = $this->_sanitizeFieldName('__crossLink_'.$crossLink['table'].
'_'.$crossLink['fromField'].
'_'.$crossLink['toField']);
if (in_array($crossLinkName, $editableFields)) {
unset($do);
$do = DB_DataObject::factory($crossLink['table']);
$fromField = $crossLink['fromField'];
$toField = $crossLink['toField'];
if (isset($values[$crossLinkName])) {
if ($crossLink['type'] == 'select') {
$fieldvalues = array();
foreach ($values[$crossLinkName] as $value) {
$fieldvalues[$value] = $value;
}
} else {
$fieldvalues = $values[$crossLinkName];
}
} else {
$fieldvalues = array();
}
/*if (isset($values['__crossLink_'.$crossLink['table'].'__extraFields'])) {
$extraFieldValues = $values['__crossLink_'.$crossLink['table'].'__extraFields'];
} else {
$extraFieldValues = array();
}*/
$links = $do->links();
list ($linkTable, $linkField) = explode(':', $links[$fromField]);
$do->$fromField = $this->_do->$linkField;
$do->selectAdd();
$do->selectAdd($toField);
$do->selectAdd($fromField);
if ($doKey = $this->_getPrimaryKey($do)) {
$do->selectAdd($doKey);
}
if ($this->isCallableAndExists($this->prepareLinkedDataObjectCallback)) {
call_user_func_array($this->prepareLinkedDataObjectCallback, array(&$do, $crossLinkName));
}
$oldFieldValues = array();
if ($do->find()) {
while ($do->fetch()) {
if (isset($fieldvalues[$do->$toField])) {
$oldFieldValues[$do->$toField] = clone($do);
} else {
if (false === $do->delete()) {
$this->debug('Failed to delete crossLink '.serialize($do));
return $this->_raiseDoError('Failed to delete crossLink', $do);
}
}
}
}
if (count($fieldvalues) > 0) {
foreach ($fieldvalues as $fieldvalue => $on) {
$crossLinkPrefix = $this->elementNamePrefix.$crossLinkName.'__'.$fieldvalue.'_';
$crossLinkPostfix = '_'.$this->elementNamePostfix;
if (isset($oldFieldValues[$fieldvalue])) {
if (isset($do->fb_crossLinkExtraFields)
&& (!isset($crossLink['type']) || ($crossLink['type'] !== 'select'))) {
$ret = $this->_extraFieldsFb[$crossLinkPrefix.$crossLinkPostfix]->processForm(isset($origValues)
? $origValues
: $values);
if (PEAR::isError($ret)) {
$this->debug('Failed to process extraFields for crossLink '.serialize($do));
return PEAR::raiseError('Failed to process extraFields crossLink - Error from processForm: '
.$ret->getMessage()
, null, null, null, $do);
}
}
} else {
if (isset($do->fb_crossLinkExtraFields)
&& (!isset($crossLink['type']) || ($crossLink['type'] !== 'select'))) {
$insertValues = isset($origValues) ? $origValues : $values;
$insertValues[$crossLinkPrefix.$fromField.$crossLinkPostfix] = $this->_do->$linkField;
$insertValues[$crossLinkPrefix.$toField.$crossLinkPostfix] = $fieldvalue;
$this->_extraFieldsFb[$crossLinkPrefix.$crossLinkPostfix]->fieldsToRender[] = $fromField;
$this->_extraFieldsFb[$crossLinkPrefix.$crossLinkPostfix]->fieldsToRender[] = $toField;
$ret = $this->_extraFieldsFb[$crossLinkPrefix.$crossLinkPostfix]->processForm($insertValues);
if (PEAR::isError($ret)) {
$this->debug('Failed to process extraFields for crossLink '.serialize($do));
return PEAR::raiseError('Failed to process extraFields crossLink - Error from processForm: '
.$ret->getMessage()
, null, null, null, $do);
}
} else {
unset($do);
$do = DB_DataObject::factory($crossLink['table']);
$do->$fromField = $this->_do->$linkField;
$do->$toField = $fieldvalue;
if (false === $do->insert()) {
$this->debug('Failed to insert crossLink '.serialize($do));
return $this->_raiseDoError('Failed to insert crossLink', $do);
}
}
}
}
}
}
}
foreach ($this->reverseLinks as $reverseLink) {
$elName = $this->_sanitizeFieldName('__reverseLink_'.$reverseLink['table'].'_'.$reverseLink['field']);
if (in_array($elName, $editableFields)) {
// Check for subforms
if (isset($this->linkElementTypes[$elName])
&& $this->linkElementTypes[$elName] == 'subForm') {
foreach($reverseLink['SFs'] as $sfkey => $subform) {
// Process each subform that was rendered.
if ($subform->validate()) {
$ret = $subform->process(array(&$reverseLink['FBs'][$sfkey], 'processForm'), false);
if (PEAR::isError($ret)) {
$this->debug('Failed to process subForm for reverseLink '.serialize($reverseLink['FBs'][$sfkey]->_do));
return PEAR::raiseError('Failed to process extraFields crossLink - Error from processForm: '
.$ret->getMessage()
, null, null, null, $reverseLink['FBs'][$sfkey]->_do);
}
}
}
} else {
unset($do);
$do = DB_DataObject::factory($reverseLink['table']);
if ($this->isCallableAndExists($this->prepareLinkedDataObjectCallback)) {
call_user_func_array($this->prepareLinkedDataObjectCallback, array(&$do, $key));
}
if (!is_array($rLinks = $do->links())) {
$rLinks = array();
}
$rPk = $this->_getPrimaryKey($do);
$rFields = $do->table();
list($lTable, $lField) = explode(':', $rLinks[$reverseLink['field']]);
if ($do->find()) {
while ($do->fetch()) {
unset($newVal);
if (isset($values[$elName][$do->$rPk])) {
if ($do->{$reverseLink['field']} != $this->_do->$lField) {
$do->{$reverseLink['field']} = $this->_do->$lField;
if (false === $do->update()) {
$this->debug('Failed to update reverseLink '.serialize($do));
return $this->_raiseDoError('Failed to update reverseLink', $do);
}
}
} elseif ($do->{$reverseLink['field']} == $this->_do->$lField) {
if (isset($reverseLink['defaultLinkValue'])) {
$do->{$reverseLink['field']} = $reverseLink['defaultLinkValue'];
if (false === $do->update()) {
$this->debug('Failed to update reverseLink '.serialize($do));
return $this->_raiseDoError('Failed to update reverseLink', $do);
}
} else {
if ($rFields[$reverseLink['field']] & DB_DATAOBJECT_NOTNULL) {
//ERROR!!
$this->debug('Checkbox in reverseLinks unset when link field may not be null');
} else {
require_once('DB/DataObject/Cast.php');
$do->{$reverseLink['field']} = DB_DataObject_Cast::sql('NULL');
if (false === $do->update()) {
$this->debug('Failed to update reverseLink '.serialize($do));
return $this->_raiseDoError('Failed to update reverseLink', $do);
}
}
}
}
}
}
}
}
}
}
if ($this->isCallableAndExists($this->postProcessFormCallback)) {
call_user_func_array($this->postProcessFormCallback, array(&$values, &$this));
}
return $dbOperations;
}
/**
* Takes a multi-dimentional array and flattens it. If a value in the array is an array,
* its keys are added as [key] to the original key.
* Ex:
* array('a' => 'a',
* 'b' => array('a' => 'a',
* 'b' => array('a' => 'a',
* 'b' => 'b')),
* 'c' => 'c')
* becomes
* array('a' => 'a',
* 'b[a]' => 'a',
* 'b[b][a]' => 'a',
* 'b[b][b]' => 'b',
* 'c' => 'c')
*
* @param array the array to convert
* @return array the flattened array
*/
/*function _multiArrayToSingleArray($arr) {
do {
$arrayFound = false;
foreach ($arr as $key => $val) {
if (is_array($val)) {
unset($arr[$key]);
foreach ($val as $key2 => $val2) {
$arr[$key.'['.$key2.']'] = $val2;
}
$arrayFound = true;
}
}
} while ($arrayFound);
return $arr;
}*/
/**
* Takes a full request array and extracts the values for this formBuilder instance.
* Removes the element name prefix and postfix
* Will only return values whose key is prefixed with the prefix and postfixed by the postfix
*
* @param array array from $_REQUEST
* @return array array indexed by real field name
*/
function _getMyValues(&$arr) {
//$arr = $this->_multiArrayToSingleArray($arr);
$retArr = array();
$prefixLen = strlen($this->elementNamePrefix);
$postfixLen = strlen($this->elementNamePostfix);
foreach ($arr as $key => $val) {
if ($prefixLen) {
if (substr($key, 0, $prefixLen) == $this->elementNamePrefix) {
$key = substr($key, $prefixLen);
} else {
$key = false;
}
}
if ($key !== false && $postfixLen) {
if (substr($key, -$postfixLen) == $this->elementNamePostfix) {
$key = substr($key, 0, -$postfixLen);
} else {
$key = false;
}
}
if ($key !== false) {
$retArr[$key] = $val;
}
}
return $retArr;
}
/**
* DB_DataObject_FormBuilder::forceQueryType()
*
* You can force the behaviour of the processForm() method by passing one of
* the following constants to this method:
*
* - DB_DATAOBJECT_FORMBUILDER_QUERY_FORCEINSERT:
* The submitted data will always be INSERTed into the database
* - DB_DATAOBJECT_FORMBUILDER_QUERY_FORCEUPDATE:
* The submitted data will always be used to perform an UPDATE on the database
* - DB_DATAOBJECT_FORMBUILDER_QUERY_FORCENOACTION:
* The submitted data will overwrite the properties of the DataObject, but no
* action will be performed on the database.
* - DB_DATAOBJECT_FORMBUILDER_QUERY_AUTODETECT:
* The processForm() method will try to detect for itself if an INSERT or UPDATE
* query has to be performed. This will not work if no primary key field can
* be detected for the current DataObject. In this case, no action will be performed.
* This is the default behaviour.
*
* @param integer $queryType The type of the query to be performed. Please use the preset constants for setting this.
* @return boolean
* @access public
*/
function forceQueryType($queryType = DB_DATAOBJECT_FORMBUILDER_QUERY_AUTODETECT)
{
switch ($queryType) {
case DB_DATAOBJECT_FORMBUILDER_QUERY_FORCEINSERT:
case DB_DATAOBJECT_FORMBUILDER_QUERY_FORCEUPDATE:
case DB_DATAOBJECT_FORMBUILDER_QUERY_FORCENOACTION:
case DB_DATAOBJECT_FORMBUILDER_QUERY_AUTODETECT:
$this->_queryType = $queryType;
return true;
break;
default:
return false;
}
}
/**
* DB_DataObject_FormBuilder::debug()
*
* Outputs a debug message, if the debug setting in the DataObject.ini file is
* set to 1 or higher.
*
* @param string $message The message to printed to the browser
* @access public
* @see DB_DataObject::debugLevel()
*/
function debug($message)
{
if (DB_DataObject::debugLevel() > 0) {
echo '<pre><b>FormBuilder:</b> '.$message."</pre>\n";
}
}
/**
* DB_DataObject_FormBuilder::_getFieldsToRender()
*
* If the "fb_fieldsToRender" property in a DataObject is not set, all fields
* will be rendered as form fields.
* When the property is set, a field will be rendered only if:
* 1. it is a primary key
* 2. it's explicitly requested in $do->fb_fieldsToRender
*
* @access private
* @return array The fields that shall be rendered
*/
function _getFieldsToRender()
{
$table = $this->_do->table();
if (!is_array($table) || !$table) {
$this->debug('ERROR: DataObject has no fields reported by table(), something is definately wrong');
$table = array();
}
$all_fields = array_merge($table, $this->_getSpecialElementNames());
if ($this->fieldsToRender) {
$fieldsToRender =& $this->fieldsToRender;
}
// a little workaround to get an array like [FIELD_NAME] => FIELD_TYPE (for use in _generateForm)
// maybe there's some better way to do this:
$result = array();
$key_fields = $this->_do->keys();
if (!is_array($key_fields) || !$key_fields) {
$this->debug('WARNING: DataObject has no keys reported by keys()');
$key_fields = array();
}
foreach ($all_fields as $key => $value) {
if (!isset($fieldsToRender)
|| in_array($key, $key_fields)
|| in_array($key, $fieldsToRender)) {
$result[$key] = $all_fields[$key];
if (isset($this->preDefGroups[$key])
&& !in_array($this->preDefGroups[$key], $result)) {
$result[$this->preDefGroups[$key]] = DB_DATAOBJECT_FORMBUILDER_GROUP;
}
}
}
if (count($result) > 0) {
return $result;
}
return $all_fields;
}
/**
* DB_DataObject_FormBuilder::_getUserEditableFields()
*
* Normally, all fields in a form are editable by the user. If you want to
* make some fields uneditable, you have to set the "fb_userEditableFields" property
* with an array that contains the field names that actually can be edited.
* All other fields will be freezed (which means, they will still be a part of
* the form, and they values will still be displayed, but only as plain text, not
* as form elements).
*
* @access private
* @return array The fields that shall be editable.
*/
function _getUserEditableFields()
{
// if you don't want any of your fields to be editable by the user, set fb_userEditableFields to
// "array()" in your DataObject-derived class
if ($this->userEditableFields) {
return $this->userEditableFields;
}
// all fields may be updated by the user since fb_userEditableFields is not set
if ($this->fieldsToRender) {
return $this->fieldsToRender;
}
return array_keys($this->_getFieldsToRender());
}
function isCallableAndExists($callback) {
return is_callable($callback)
&& (is_array($callback)
? (is_object($callback[0])
? method_exists($callback[0], $callback[1])
: true)
: function_exists($callback));
}
}
?>