/relations/class.atkmanytoonerelation.inc
PHP | 2483 lines | 1458 code | 242 blank | 783 comment | 257 complexity | c0bf5b202591ec9c534a0eba89cd1723 MD5 | raw file
Possible License(s): LGPL-2.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, LGPL-3.0
Large files files are truncated, but you can click here to view the full file
- <?php
- /**
- * This file is part of the Achievo ATK distribution.
- * Detailed copyright and licensing information can be found
- * in the doc/COPYRIGHT and doc/LICENSE files which should be
- * included in the distribution.
- *
- * @package atk
- * @subpackage relations
- *
- * @copyright (c)2000-2004 Ibuildings.nl BV
- * @copyright (c)2000-2004 Ivo Jansch
- * @license http://www.achievo.org/atk/licensing ATK Open Source License
- *
- * @version $Revision: 6309 $
- * $Id$
- */
- /**
- * Create edit/view links for the items in a manytoonerelation dropdown.
- */
- define("AF_RELATION_AUTOLINK", AF_SPECIFIC_1);
- /**
- * Create edit/view links for the items in a manytoonerelation dropdown.
- */
- define("AF_MANYTOONE_AUTOLINK", AF_RELATION_AUTOLINK);
- /**
- * Do not add null option under any circumstance
- */
- define("AF_RELATION_NO_NULL_ITEM", AF_SPECIFIC_2);
- /**
- * Do not add null option ever
- */
- define("AF_MANYTOONE_NO_NULL_ITEM", AF_RELATION_NO_NULL_ITEM);
- /**
- * Use auto-completition instead of drop-down / selection page
- */
- define("AF_RELATION_AUTOCOMPLETE", AF_SPECIFIC_3);
- /**
- * Use auto-completition instead of drop-down / selection page
- */
- define("AF_MANYTOONE_AUTOCOMPLETE", AF_RELATION_AUTOCOMPLETE);
- /**
- * Lazy load
- */
- define("AF_MANYTOONE_LAZY", AF_SPECIFIC_4);
- /**
- * Add a default null option to obligatory relations
- */
- define("AF_MANYTOONE_OBLIGATORY_NULL_ITEM", AF_SPECIFIC_5);
- /**
- * @internal include base class
- */
- userelation("atkrelation");
- /**
- * A N:1 relation between two classes.
- *
- * For example, projects all have one coordinator, but one
- * coordinator can have multiple projects. So in the project
- * class, there's a ManyToOneRelation to a coordinator.
- *
- * This relation essentially creates a dropdown box, from which
- * you can select from a set of records.
- *
- * @author Ivo Jansch <ivo@achievo.org>
- * @package atk
- * @subpackage relations
- *
- */
- class atkManyToOneRelation extends atkRelation
- {
- const SEARCH_MODE_EXACT = "exact";
- const SEARCH_MODE_STARTSWITH = "startswith";
- const SEARCH_MODE_CONTAINS = "contains";
- /**
- * By default, we do a left join. this means that records that don't have
- * a record in this relation, will be displayed anyway. NOTE: set this to
- * false only if you know what you're doing. When in doubt, 'true' is
- * usually the best option.
- * @var boolean
- */
- var $m_leftjoin = true;
- /**
- * The array of referential key fields.
- * @access private
- * @var array
- */
- var $m_refKey = array();
- /**
- * SQL statement with extra filter for the join that retrieves the
- * selected record.
- * @var String
- */
- var $m_joinFilter = "";
- /**
- * Hide the relation when there are no records to select.
- * @access private
- * @var boolean
- */
- var $m_hidewhenempty = false;
- /**
- * List columns.
- * @access private
- * @var Array
- */
- var $m_listColumns = array();
- /**
- * Always show list columns?
- * @access private
- * @var boolean
- */
- var $m_alwaysShowListColumns = false;
- /**
- * Label to use for the 'none' option.
- *
- * @access private
- * @var String
- */
- var $m_noneLabel = NULL;
- /**
- * Minimum number of character a user needs to enter before auto-completion kicks in.
- *
- * @access private
- * @var int
- */
- var $m_autocomplete_minchars = 2;
- /**
- * An array with the fieldnames of the destination node in which the autocompletion must search
- * for results.
- *
- * @access private
- * @var array
- */
- var $m_autocomplete_searchfields = "";
- /**
- * The search mode of the autocomplete fields. Can be 'startswith', 'exact' or 'contains'.
- *
- * @access private
- * @var String
- */
- var $m_autocomplete_searchmode = "contains";
- /**
- * Value determines wether the search of the autocompletion is case-sensitive.
- *
- * @var boolean
- */
- var $m_autocomplete_search_case_sensitive = false;
- /**
- * Value determines if select link for autocomplete should use atkSubmit or not (for use in admin screen for example)
- *
- * @var boolean
- */
- var $m_autocomplete_saveform = true;
- /**
- * Set the minimal number of records for showing the automcomplete. If there are less records
- * the normal dropdown is shown
- *
- * @access private
- * @var integer
- */
- var $m_autocomplete_minrecords = -1;
- /**
- * Destination node for auto links (edit, new)
- *
- * @var string
- */
- protected $m_autolink_destination = "";
- // override onchangehandler init
- var $m_onchangehandler_init = "newvalue = el.options[el.selectedIndex].value;\n";
- /**
- * Use destination filter for autolink add link?
- *
- * @access private
- * @var boolean
- */
- var $m_useFilterForAddLink = true;
- /**
- * Set a function to use for determining the descriptor in the getConcatFilter function
- *
- * @access private
- * @var string
- */
- var $m_concatDescriptorFunction = '';
- /**
- * When autosearch is set to true, this attribute will automatically submit
- * the search form onchange. This will only happen in the admin action.
- *
- * @var boolean
- */
- protected $m_autoSearch = false;
- /**
- * Selectable records for edit mode.
- *
- * @see atkManyToOneRelation::preAddToEditArray
- *
- * @var array
- */
- protected $m_selectableRecords = null;
- /**
- * Constructor.
- * @param String $name The name of the attribute. This is the name of the
- * field that is the referential key to the
- * destination.
- * For relations with more than one field in the
- * foreign key, you should pass an array of
- * referential key fields. The order of the fields
- * must match the order of the primary key attributes
- * in the destination node.
- * @param String $destination The node we have a relationship with.
- * @param int $flags Flags for the relation
- */
- function atkManyToOneRelation($name, $destination, $flags=0)
- {
- if (atkconfig("manytoone_autocomplete_default", false))
- $flags |= AF_RELATION_AUTOCOMPLETE;
- if (atkconfig("manytoone_autocomplete_large", true) && hasFlag($flags, AF_LARGE))
- $flags |= AF_RELATION_AUTOCOMPLETE;
- $this->m_autocomplete_minchars = atkconfig("manytoone_autocomplete_minchars", 2);
- $this->m_autocomplete_searchmode = atkconfig("manytoone_autocomplete_searchmode", "contains");
- $this->m_autocomplete_search_case_sensitive = atkconfig("manytoone_autocomplete_search_case_sensitive", false);
- if (is_array($name))
- {
- $this->m_refKey = $name;
- // ATK can't handle an array as name, so we initialize the
- // underlying attribute with the first name of the referential
- // keys.
- // Languagefiles, overrides, etc should use this first name to
- // override the relation.
- $this->atkRelation($name[0], $destination, $flags);
- }
- else
- {
- $this->m_refKey[] = $name;
- $this->atkRelation($name, $destination, $flags);
- }
- if ($this->hasFlag(AF_MANYTOONE_LAZY) && (count($this->m_refKey) > 1 || $this->m_refKey[0] != $this->fieldName()))
- {
- atkerror("AF_MANYTOONE_LAZY flag is not supported for multi-column reference key or a reference key that uses another column.");
- }
- }
- /**
- * Adds a flag to the manyToOne relation
- * Note that adding flags at any time after the constructor might not
- * always work. There are flags that are processed only at
- * constructor time.
- *
- * @param int $flag The flag to add to the attribute
- * @return atkManyToOneRelation The instance of this atkManyToOneRelation
- */
- function addFlag($flag)
- {
- parent::addFlag($flag);
- if (atkconfig("manytoone_autocomplete_large", true) && hasFlag($flag, AF_LARGE))
- $this->m_flags |= AF_RELATION_AUTOCOMPLETE;
- return $this;
- }
- /**
- * When autosearch is set to true, this attribute will automatically submit
- * the search form onchange. This will only happen in the admin action.
- * @param bool $auto
- * @return void
- */
- public function setAutoSearch($auto = false)
- {
- $this->m_autoSearch = $auto;
- }
- /**
- * Set join filter.
- *
- * @param string $filter join filter
- */
- function setJoinFilter($filter)
- {
- $this->m_joinFilter = $filter;
- }
- /**
- * Set the searchfields for the autocompletion.
- *
- * @param array $searchfields
- */
- function setAutoCompleteSearchFields($searchfields)
- {
- $this->m_autocomplete_searchfields = $searchfields;
- }
- /**
- * Set the searchmode for the autocompletion:
- * exact, startswith(default) or contains.
- *
- * @param array $mode
- */
- function setAutoCompleteSearchMode($mode)
- {
- $this->m_autocomplete_searchmode = $mode;
- }
- /**
- * Set the case-sensitivity for the autocompletion search (true or false).
- *
- * @param array $case_sensitive
- */
- function setAutoCompleteCaseSensitive($case_sensitive)
- {
- $this->m_autocomplete_search_case_sensitive = $case_sensitive;
- }
- /**
- * Sets the minimum number of characters before auto-completion kicks in.
- *
- * @param int $chars
- */
- function setAutoCompleteMinChars($chars)
- {
- $this->m_autocomplete_minchars = $chars;
- }
- /**
- * Set if the select link should save form (atkSubmit) or not (for use in admin screen for example)
- *
- * @param boolean $saveform
- */
- function setAutoCompleteSaveForm($saveform=true)
- {
- $this->m_autocomplete_saveform = $saveform;
- }
- /**
- * Set the minimal number of records for the autocomplete to show
- * If there are less records the normal dropdown is shown
- *
- * @param integer $minrecords
- */
- function setAutoCompleteMinRecords($minrecords)
- {
- $this->m_autocomplete_minrecords = $minrecords;
- }
- /**
- * Use destination filter for auto add link?
- *
- * @param boolean $useFilter use destnation filter for add link?
- */
- function setUseFilterForAddLink($useFilter)
- {
- $this->m_useFilterForAddLink = $useFilter;
- }
- /**
- * Set the function for determining the descriptor in the getConcatFilter function
- * This function should be implemented in the destination node
- *
- * @param string $function
- */
- function setConcatDescriptorFunction($function)
- {
- $this->m_concatDescriptorFunction = $function;
- }
- /**
- * Return the function for determining the descriptor in the getConcatFilter function
- *
- * @return string
- */
- function getConcatDescriptorFunction()
- {
- return $this->m_concatDescriptorFunction;
- }
- /**
- * Add list column. An attribute of the destination node
- * that (only) will be displayed in the recordlist.
- *
- * @param string $attr The attribute to add to the listcolumn
- * @return atkManyToOneRelation The instance of this atkManyToOneRelation
- */
- function addListColumn($attr)
- {
- $this->m_listColumns[] = $attr;
- return $this;
- }
- /**
- * Add multiple list columns. Attributes of the destination node
- * that (only) will be displayed in the recordlist.
- * @return atkManyToOneRelation The instance of this atkManyToOneRelation
- */
- function addListColumns()
- {
- $attrs = func_get_args();
- foreach ($attrs as $attr)
- $this->m_listColumns[] = $attr;
- return $this;
- }
- public function getListColumns()
- {
- return $this->m_listColumns;
- }
- /**
- * Reset the list columns and add multiple list columns. Attributes of the
- * destination node that (only) will be displayed in the recordlist.
- * @return atkManyToOneRelation The instance of this atkManyToOneRelation
- */
- public function setListColumns()
- {
- $this->m_listColumns = array();
- $attrs = func_get_args();
- if (count($attrs)===1 && is_array($attrs[0]))
- {
- $columns = $attrs[0];
- }
- else
- {
- $columns = $attrs;
- }
-
- foreach ($columns as $column)
- {
- $this->m_listColumns[] = $column;
- }
- return $this;
- }
- /**
- * Always show list columns in list view,
- * even if the attribute itself is hidden?
- *
- * @param bool $value always show list columns?
- * @return atkManyToOneRelation The instance of this atkManyToOneRelation
- */
- function setAlwaysShowListColumns($value)
- {
- $this->m_alwaysShowListColumns = $value;
- if($this->m_alwaysShowListColumns)
- $this->addFlag(AF_FORCE_LOAD);
- return $this;
- }
- /**
- * Convert value to DataBase value
- * @param array $rec Record to convert
- * @return int Database safe value
- */
- function value2db($rec)
- {
- if ($this->isEmpty($rec))
- {
- atkdebug($this->fieldName()." IS EMPTY!");
- return NULL;
- }
- else
- {
- if ($this->createDestination())
- {
- if (is_array($rec[$this->fieldName()]))
- {
- $pkfield = $this->m_destInstance->m_primaryKey[0];
- $pkattr = &$this->m_destInstance->getAttribute($pkfield);
- return $pkattr->value2db($rec[$this->fieldName()]);
- }
- else
- {
- return $rec[$this->fieldName()];
- }
- }
- }
- // This never happens, does it?
- return "";
- }
- /**
- * Fetch value out of record
- * @param array $postvars Postvars
- * @return decoded value
- */
- function fetchValue($postvars)
- {
- if ($this->isPosted($postvars))
- {
- $result = array();
- // support specifying the value as a single number if the
- // destination's primary key consists of a single field
- if (is_numeric($postvars[$this->fieldName()]))
- {
- $result[$this->getDestination()->primaryKeyField()] = $postvars[$this->fieldName()];
- }
- else
- {
- // Split the primary key of the selected record into its
- // referential key elements.
- $keyelements = decodeKeyValueSet($postvars[$this->fieldName()]);
- foreach ($keyelements as $key=>$value)
- {
- // Tablename must be stripped out because it is in the way..
- if (strpos($key,'.')>0)
- {
- $field = substr($key,strrpos($key,'.')+1);
- }
- else
- {
- $field = $key;
- }
- $result[$field] = $value;
- }
- }
- if (count($result) == 0)
- {
- return null;
- }
- // add descriptor fields, this means they can be shown in the title
- // bar etc. when updating failed for example
- $record = array($this->fieldName() => $result);
- $this->populate($record);
- $result = $record[$this->fieldName()];
- return $result;
- }
- return NULL;
- }
- /**
- * Converts DataBase value to normal value
- * @param array $rec Record
- * @return decoded value
- */
- function db2value($rec)
- {
- $this->createDestination();
- if (isset($rec[$this->fieldName()]) &&
- is_array($rec[$this->fieldName()]) &&
- (!isset($rec[$this->fieldName()][$this->m_destInstance->primaryKeyField()]) ||
- empty($rec[$this->fieldName()][$this->m_destInstance->primaryKeyField()])))
- {
- return NULL;
- }
- if (isset($rec[$this->fieldName()]))
- {
- $myrec = $rec[$this->fieldName()];
- if (is_array($myrec))
- {
- $result = array();
- if ($this->createDestination())
- {
- foreach (array_keys($this->m_destInstance->m_attribList) as $attrName)
- {
- $attr = &$this->m_destInstance->m_attribList[$attrName];
- $result[$attrName] = $attr->db2value($myrec);
- }
- }
- return $result;
- }
- else
- {
- // if the record is not an array, probably only the value of the primary key was loaded.
- // This workaround only works for single-field primary keys.
- if ($this->createDestination())
- return array($this->m_destInstance->primaryKeyField() => $myrec);
- }
- }
- }
- /**
- * Set none label.
- *
- * @param string $label The label to use for the "none" option
- */
- function setNoneLabel($label)
- {
- $this->m_noneLabel = $label;
- }
- /**
- * Get none label.
- *
- * @return String The label for the "none" option
- */
- function getNoneLabel()
- {
- if ($this->m_noneLabel !== NULL)
- return $this->m_noneLabel;
- $nodename = $this->m_destInstance->m_type;
- $modulename = $this->m_destInstance->m_module;
- $ownermodulename = $this->m_ownerInstance->m_module;
- $label = atktext($this->fieldName() . '_select_none', $ownermodulename, $this->m_owner, "", "", true);
- if ($label == "")
- $label = atktext('select_none', $modulename, $nodename);
- return $label;
- }
- /**
- * Returns a displayable string for this value.
- *
- * @param array $record The record that holds the value for this attribute
- * @param String $mode The display mode ("view" for viewpages, or "list"
- * for displaying in recordlists, "edit" for
- * displaying in editscreens, "add" for displaying in
- * add screens. "csv" for csv files. Applications can
- * use additional modes.
- * @return a displayable string
- */
- function display($record, $mode='list')
- {
- if ($this->createDestination())
- {
- if (count($record[$this->fieldName()])==count($this->m_refKey))
- $this->populate($record);
- if(!$this->isEmpty($record))
- {
- $result = $this->m_destInstance->descriptor($record[$this->fieldName()]);
- if ($this->hasFlag(AF_RELATION_AUTOLINK) && (!in_array($mode, array("csv", "plain", "list")))) // create link to edit/view screen
- {
- if (($this->m_destInstance->allowed("view"))&&!$this->m_destInstance->hasFlag(NF_NO_VIEW)&&$result!="")
- {
- $saveForm = $mode == 'add' || $mode == 'edit';
- $result = href(dispatch_url($this->m_destination,"view",array("atkselector"=>$this->m_destInstance->primaryKey($record[$this->fieldName()]))), $result, SESSION_NESTED, $saveForm);
- }
- }
- }
- else
- {
- $result = (!in_array($mode, array("csv", "plain"))?$this->getNoneLabel():''); // no record
- }
- return $result;
- }
- else
- {
- atkdebug("Can't create destination! ($this->m_destination");
- }
- return "";
- }
- /**
- * Populate the record with the destination record data.
- *
- * @param array $record record
- * @param mixed $fullOrFields load all data, only the given fields or only the descriptor fields?
- */
- public function populate(&$record, $fullOrFields=false)
- {
- if (!is_array($record)||$record[$this->fieldName()] == "") return;
- atkdebug("Delayed loading of ".($fullOrFields || is_array($fullOrFields) ? "" : "descriptor ")."fields for ".$this->m_name);
- $this->createDestination();
- $includes = "";
- if (is_array($fullOrFields))
- {
- $includes = array_merge($this->m_destInstance->m_primaryKey, $fullOrFields);
- }
- else if (!$fullOrFields)
- {
- $includes = $this->m_destInstance->descriptorFields();
- }
- $result =
- $this->m_destInstance
- ->select($this->m_destInstance->primaryKey($record[$this->fieldName()]))
- ->orderBy($this->m_destInstance->getColumnConfig()->getOrderByStatement())
- ->includes($includes)
- ->firstRow();
-
- if ($result != null)
- {
- $record[$this->fieldName()] = $result;
- }
- }
- /**
- * Creates HTML for the selection and auto links.
- *
- * @param string $id attribute id
- * @param array $record record
- * @return string
- */
- function createSelectAndAutoLinks($id, $record)
- {
- $links = array();
- $newsel = $id;
- $filter = $this->parseFilter($this->m_destinationFilter, $record);
- $links[] = $this->_getSelectLink($newsel, $filter);
- if ($this->hasFlag(AF_RELATION_AUTOLINK)) // auto edit/view link
- {
- if ($this->m_destInstance->allowed("add"))
- {
- $links[] = href(dispatch_url($this->getAutoLinkDestination(),"add",array("atkpkret"=>$id,"atkfilter"=>($filter!=""?$filter:""))),
- atktext("new"),SESSION_NESTED,true);
- }
- if ($this->m_destInstance->allowed("edit") && $record[$this->fieldName()] != NULL)
- {
- //we laten nu altijd de edit link zien, maar eigenlijk mag dat niet, want
- //de app crasht als er geen waarde is ingevuld.
- $editUrl = session_url(dispatch_url($this->getAutoLinkDestination(),"edit",array("atkselector"=>"REPLACEME")),SESSION_NESTED);
- $links[] = "<span id=\"".$id."_edit\" style=\"\"><a href='javascript:atkSubmit(mto_parse(\"".atkurlencode($editUrl)."\", document.entryform.".$id.".value))'>".atktext('edit')."</a></span>";
- }
- }
- return implode(" ", $links);
- }
- /**
- * Set destination node for the Autolink links (new/edit)
- *
- * @param string $node
- */
- function setAutoLinkDestination($node)
- {
- $this->m_autolink_destination = $node;
- }
- /**
- * Get destination node for the Autolink links (new/edit)
- *
- * @return string
- */
- function getAutoLinkDestination()
- {
- if(!empty($this->m_autolink_destination))
- {
- return $this->m_autolink_destination;
- }
- return $this->m_destination;
- }
- /**
- * Prepare for editing, make sure we already have the selectable records
- * loaded and update the record with the possible selection of the first
- * record.
- *
- * @param array $record reference to the record
- * @param string $fieldPrefix field prefix
- * @param string $mode edit mode
- */
- public function preAddToEditArray(&$record, $fieldPrefix, $mode)
- {
- if ((!$this->hasFlag(AF_RELATION_AUTOCOMPLETE) && !$this->hasFlag(AF_LARGE)) ||
- $this->m_autocomplete_minrecords > -1)
- {
- $this->m_selectableRecords = $this->_getSelectableRecords($record, 'select');
- if (count($this->m_selectableRecords) > 0 &&
- !atkconfig("list_obligatory_null_item") &&
- (($this->hasFlag(AF_OBLIGATORY) && !$this->hasFlag(AF_MANYTOONE_OBLIGATORY_NULL_ITEM)) ||
- (!$this->hasFlag(AF_OBLIGATORY) && $this->hasFlag(AF_RELATION_NO_NULL_ITEM))))
- {
- if (!isset($record[$this->fieldName()]) || !is_array($record[$this->fieldName()]))
- {
- $record[$this->fieldName()] = $this->m_selectableRecords[0];
- }
- else if (!$this->_isSelectableRecord($record, 'select'))
- {
- $record[$this->fieldName()] = $this->m_selectableRecords[0];
- }
- else
- {
- $current = $this->getDestination()->primaryKey($record[$this->fieldName()]);
- $record[$this->fieldName()] = null;
- foreach ($this->m_selectableRecords as $selectable)
- {
- if ($this->getDestination()->primaryKey($selectable) == $current)
- {
- $record[$this->fieldName()] = $selectable;
- break;
- }
- }
- }
- }
- }
- else if (is_array($record[$this->fieldName()]) && !$this->_isSelectableRecord($record, 'select'))
- {
- $record[$this->fieldName()] = null;
- }
- else if (is_array($record[$this->fieldName()]))
- {
- $this->populate($record);
- }
- }
- /**
- * Returns a piece of html code that can be used in a form to edit this
- * attribute's value.
- * @param array $record The record that holds the value for this attribute.
- * @param String $fieldprefix The fieldprefix to put in front of the name
- * of any html form element for this attribute.
- * @param String $mode The mode we're in ('add' or 'edit')
- * @return Piece of html code that can be used in a form to edit this
- */
- function edit($record, $fieldprefix="", $mode="edit")
- {
- if (!$this->createDestination())
- {
- return atkerror("Could not create destination for destination: $this->m_destination!");
- }
- $recordset = $this->m_selectableRecords;
- // load records for bwc
- if ($recordset === null && $this->hasFlag(AF_RELATION_AUTOCOMPLETE) && $this->m_autocomplete_minrecords > -1)
- {
- $recordset = $this->_getSelectableRecords($record, 'select');
- }
- if ($this->hasFlag(AF_RELATION_AUTOCOMPLETE) && (is_object($this->m_ownerInstance)) && ((is_array($recordset) && count($recordset) > $this->m_autocomplete_minrecords) || $this->m_autocomplete_minrecords == -1))
- {
- return $this->drawAutoCompleteBox($record, $fieldprefix, $mode);
- }
- $id = $fieldprefix.$this->fieldName();
- $filter = $this->parseFilter($this->m_destinationFilter, $record);
- $autolink = $this->getRelationAutolink($id, $filter);
- $editflag = true;
- $value = isset($record[$this->fieldName()]) ? $record[$this->fieldName()] : null;
- $currentPk = $value != null ? $this->getDestination()->primaryKey($value) : null;
- if (!$this->hasFlag(AF_LARGE)) // normal dropdown..
- {
- // load records for bwc
- if ($recordset == null)
- {
- $recordset = $this->_getSelectableRecords($record, 'select');
- }
- if (count($recordset) == 0)
- {
- $editflag = false;
- }
- $onchange='';
- if (count($this->m_onchangecode))
- {
- $onchange = 'onChange="'.$id.'_onChange(this);"';
- $this->_renderChangeHandler($fieldprefix);
- }
- // autoselect if there is only one record (if obligatory is not set,
- // we don't autoselect, since user may wist to select 'none' instead
- // of the 1 record.
- $result = '';
- if (count($recordset) == 0)
- {
- $result = $this->getNoneLabel();
- }
- else
- {
- $this->registerKeyListener($id, KB_CTRLCURSOR|KB_LEFTRIGHT);
- $this->registerJavaScriptObservers($id);
- $result = '<select id="'.$id.'" name="'.$id.'" class="atkmanytoonerelation" '.$onchange.'>';
- // relation may be empty, so we must provide an empty selectable..
- if ($this->hasFlag(AF_MANYTOONE_OBLIGATORY_NULL_ITEM) ||
- (!$this->hasFlag(AF_OBLIGATORY) && !$this->hasFlag(AF_RELATION_NO_NULL_ITEM)) ||
- (atkconfig("list_obligatory_null_item") && !is_array($value)))
- {
- $result.= '<option value="">'.$this->getNoneLabel();
- }
- foreach ($recordset as $selectable)
- {
- $pk = $this->getDestination()->primaryKey($selectable);
- $sel = $pk == $currentPk ? 'selected="selected"' : '';
- $result .= '<option value="'.$pk.'" '.$sel.'>'.str_replace(' ', ' ', atk_htmlentities($this->m_destInstance->descriptor($selectable)));
- }
- $result .= '</select>';
- }
- }
- else
- {
- $destrecord = $record[$this->fieldName()];
- if (is_array($destrecord))
- {
- $result.= '<span id="'.$id.'_current" >'.$this->m_destInstance->descriptor($destrecord)." ";
- if (!$this->hasFlag(AF_OBLIGATORY))
- {
- $result.= '<a href="#" onClick="document.getElementById(\''.
- $id.'\').value=\'\'; document.getElementById(\''.$id.'_current\').style.display=\'none\'">'.atktext("unselect").'</a> ';
- }
- $result.= '</span>';
- }
- $result .= $this->hide($record, $fieldprefix);
- $result .= $this->_getSelectLink($id, $filter);
- }
- $result .= $editflag && isset($autolink['edit']) ? $autolink['edit'] : "";
- $result .= isset($autolink['add']) ? $autolink['add'] : "";
- return $result;
- }
- /**
- * Get the select link to select the value using a select action on the destination node
- *
- * @param string $selname
- * @param string $filter
- * @return String HTML-code with the select link
- */
- function _getSelectLink($selname, $filter)
- {
- $result = "";
- // we use the current level to automatically return to this page
- // when we come from the select..
- $atktarget = atkurlencode(getDispatchFile()."?atklevel=".atkLevel()."&".$selname."=[atkprimkey]");
- $linkname = atktext("link_select_".getNodeType($this->m_destination), $this->getOwnerInstance()->getModule(), $this->getOwnerInstance()->getType(),'','',true);
- if (!$linkname) $linkname = atktext("link_select_".getNodeType($this->m_destination), getNodeModule($this->m_destination),getNodeType($this->m_destination),'','',true);
- if (!$linkname) $linkname = atktext("select_a").' '.strtolower(atktext(getNodeType($this->m_destination), getNodeModule($this->m_destination),getNodeType($this->m_destination)));
- if ($this->m_destinationFilter!="")
- {
- $result.= href(dispatch_url($this->m_destination,"select",array("atkfilter"=>$filter,"atktarget"=>$atktarget)),
- $linkname,
- SESSION_NESTED,
- $this->m_autocomplete_saveform,'class="atkmanytoonerelation"');
- }
- else
- {
- $result.= href(dispatch_url($this->m_destination,"select",array("atktarget"=>$atktarget)),
- $linkname,
- SESSION_NESTED,
- $this->m_autocomplete_saveform,'class="atkmanytoonerelation"');
- }
- return $result;
- }
- /**
- * Creates and returns the auto edit/view links
- * @param String $id The field id
- * @param String $filter Filter that we want to apply on the destination node
- * @return array The HTML code for the autolink links
- */
- function getRelationAutolink($id, $filter)
- {
- $autolink = array();
- if ($this->hasFlag(AF_RELATION_AUTOLINK)) // auto edit/view link
- {
- $page = &atkPage::getInstance();
- $page->register_script(atkconfig("atkroot")."atk/javascript/class.atkmanytoonerelation.js");
- if ($this->m_destInstance->allowed("edit"))
- {
- $editlink = session_url(dispatch_url($this->getAutoLinkDestination(),"edit",array("atkselector"=>"REPLACEME")),SESSION_NESTED);
- $autolink['edit'] = " <a href='javascript:atkSubmit(mto_parse(\"".atkurlencode($editlink)."\", document.entryform.".$id.".value))'>".atktext('edit')."</a>";
- }
- if ($this->m_destInstance->allowed("add"))
- {
- $autolink['add'] = " ".href(dispatch_url($this->getAutoLinkDestination(),"add",array("atkpkret"=>$id,"atkfilter"=>($this->m_useFilterForAddLink && $filter != "" ? $filter : ""))),
- atktext("new"),SESSION_NESTED,true);
- }
- }
- return $autolink;
- }
- /**
- * Returns a piece of html code for hiding this attribute in an HTML form,
- * while still posting its value. (<input type="hidden">)
- *
- * @param array $record The record that holds the value for this attribute
- * @param String $fieldprefix The fieldprefix to put in front of the name
- * of any html form element for this attribute.
- * @return String A piece of htmlcode with hidden form elements that post
- * this attribute's value without showing it.
- */
- function hide($record="", $fieldprefix="")
- {
- if (!$this->createDestination()) return '';
- $currentPk = "";
- if (isset($record[$this->fieldName()]) && $record[$this->fieldName()] != null)
- {
- $this->fixDestinationRecord($record);
- $currentPk = $this->m_destInstance->primaryKey($record[$this->fieldName()]);
- }
- $result =
- '<input type="hidden" id="'.$fieldprefix.$this->formName().'"
- name="'.$fieldprefix.$this->formName().'"
- value="'.$currentPk.'">';
- return $result;
- }
- /**
- * Support for destination "records" where only the id is set and the
- * record itself isn't converted to a real record (array) yet
- *
- * @param array $record The record to fix
- */
- function fixDestinationRecord(&$record)
- {
- if ($this->createDestination() &&
- isset($record[$this->fieldName()]) &&
- $record[$this->fieldName()] != null &&
- !is_array($record[$this->fieldName()]))
- {
- $record[$this->fieldName()] = array($this->m_destInstance->primaryKeyField() => $record[$this->fieldName()]);
- }
- }
- /**
- * Retrieve the html code for placing this attribute in an edit page.
- *
- * The difference with the edit() method is that the edit() method just
- * generates the HTML code for editing the attribute, while the getEdit()
- * method is 'smart', and implements a hide/readonly policy based on
- * flags and/or custom override methodes in the node.
- * (<attributename>_edit() and <attributename>_display() methods)
- *
- * Framework method, it should not be necessary to call this method
- * directly.
- *
- * @param String $mode The edit mode ("add" or "edit")
- * @param array $record The record holding the values for this attribute
- * @param String $fieldprefix The fieldprefix to put in front of the name
- * of any html form element for this attribute.
- * @return String the HTML code for this attribute that can be used in an
- * editpage.
- */
- function getEdit($mode, &$record, $fieldprefix)
- {
- $this->fixDestinationRecord($record);
- return parent::getEdit($mode, $record, $fieldprefix);
- }
- /**
- * Converts a record filter to a record array.
- *
- * @param string $filter filter string
- * @return array record
- */
- protected function filterToArray($filter)
- {
- $result = array();
- $values = decodeKeyValueSet($filter);
- foreach ($values as $field => $value)
- {
- $parts = explode('.', $field);
- $ref = &$result;
- foreach ($parts as $part)
- {
- $ref = &$ref[$part];
- }
- $ref = $value;
- }
- return $result;
- }
- /**
- * Returns a piece of html code that can be used to get search terms input
- * from the user.
- *
- * @param array $record Array with values
- * @param boolean $extended if set to false, a simple search input is
- * returned for use in the searchbar of the
- * recordlist. If set to true, a more extended
- * search may be returned for the 'extended'
- * search page. The atkAttribute does not
- * make a difference for $extended is true, but
- * derived attributes may reimplement this.
- * @param string $fieldprefix The fieldprefix of this attribute's HTML element.
- * @param atkDataGrid $grid The datagrid
- *
- * @return String A piece of html-code
- */
- function search($record=array(), $extended=false, $fieldprefix="", atkDataGrid $grid=null)
- {
- $useautocompletion = atkConfig("manytoone_search_autocomplete", true) && $this->hasFlag(AF_RELATION_AUTOCOMPLETE);
- if (!$this->hasFlag(AF_LARGE) && !$useautocompletion)
- {
- if ($this->createDestination())
- {
- if ($this->m_destinationFilter!="")
- {
- $filterRecord = array();
- if ($grid != null)
- {
- foreach ($grid->getFilters() as $filter)
- {
- $filter = $filter['filter'];
- $arr = $this->filterToArray($filter);
- $arr = (isset($arr[$this->getOwnerInstance()->m_table]) && is_array($arr[$this->getOwnerInstance()->m_table])) ? $arr[$this->getOwnerInstance()->m_table] : array();
- foreach ($arr as $attrName => $value)
- {
- $attr = $this->getOwnerInstance()->getAttribute($attrName);
- if (!is_array($value) && is_a($attr, 'atkManyToOneRelation') && count($attr->m_refKey) == 1)
- {
- $attr->createDestination();
- $arr[$attrName] = array($attr->getDestination()->primaryKeyField() => $value);
- }
- }
- $filterRecord = array_merge($filterRecord, $arr);
- }
- }
- $record = array_merge($filterRecord, is_array($record) ? $record : array());
- }
- $recordset = $this->_getSelectableRecords($record, 'search');
- $result = '<select class="'.get_class($this).'" ';
- if ($extended)
- {
- $result.='multiple size="'.min(5,count($recordset)+1).'"';
- if(isset($record[$this->fieldName()][$this->fieldName()]))
- $record[$this->fieldName()] = $record[$this->fieldName()][$this->fieldName()];
- }
- // if we use autosearch, register an onchange event that submits the grid
- if (!is_null($grid) && !$extended && $this->m_autoSearch) {
- $id = $this->getSearchFieldName($fieldprefix);
- $result .= ' id="'.$id.'" ';
- $code = '$(\''.$id.'\').observe(\'change\', function(event) { ' .
- $grid->getUpdateCall(array('atkstartat' => 0), array(), 'ATK.DataGrid.extractSearchOverrides') .
- ' return false; });'
- ;
- $this->getOwnerInstance()->getPage()->register_loadscript($code);
- }
- $result.='name="'.$this->getSearchFieldName($fieldprefix).'[]">';
- $pkfield = $this->m_destInstance->primaryKeyField();
- $result.= '<option value="">'.atktext('search_all').'</option>';
- if (!$this->hasFlag(AF_OBLIGATORY))
- $result.= '<option value="__NONE__"'.(isset($record[$this->fieldName()]) && atk_in_array('__NONE__', $record[$this->fieldName()]) ? ' selected="selected"' : '').'>'.atktext('search_none').'</option>';
- for ($i=0;$i<count($recordset);$i++)
- {
- $pk = $recordset[$i][$pkfield];
- if (is_array($record)&&isset($record[$this->fieldName()])&&
- atk_in_array($pk, $record[$this->fieldName()])) $sel = "selected"; else $sel = "";
- $result.= '<option value="'.$pk.'" '.$sel.'>'.str_replace(' ', ' ', atk_htmlentities($this->m_destInstance->descriptor($recordset[$i]))).'</option>';
- }
- $result.='</select>';
- return $result;
- }
- return "";
- }
- else
- {
- $id = $this->getSearchFieldName($fieldprefix);
- if(is_array($record[$this->fieldName()]) && isset($record[$this->fieldName()][$this->fieldName()]))
- $record[$this->fieldName()] = $record[$this->fieldName()][$this->fieldName()];
- $this->registerKeyListener($id, KB_CTRLCURSOR|KB_UPDOWN);
- $result = '<input type="text" id="'.$id.'" class="'.get_class($this).'" name="'.$id.'" value="'.$record[$this->fieldName()].'"'.
- ($useautocompletion ? ' onchange=""' : '').
- ($this->m_searchsize > 0 ? ' size="'.$this->m_searchsize.'"' : '').
- ($this->m_maxsize > 0 ? ' maxlength="'.$this->m_maxsize.'"' : '').'>';
- if ($useautocompletion)
- {
- $page = &$this->m_ownerInstance->getPage();
- $url = partial_url($this->m_ownerInstance->atkNodeType(), $this->m_ownerInstance->m_action, 'attribute.'.$this->fieldName().'.autocomplete_search');
- $code = "ATK.ManyToOneRelation.completeSearch('{$id}', '{$id}_result', '{$url}', {$this->m_autocomplete_minchars});";
- $this->m_ownerInstance->addStyle("atkmanytoonerelation.css");
- $page->register_script(atkconfig('atkroot').'atk/javascript/class.atkmanytoonerelation.js');
- $page->register_loadscript($code);
- $result .= '<div id="'.$id.'_result" style="display: none" class="atkmanytoonerelation_result"></div>';
- }
- return $result;
- }
- }
- /**
- * Retrieve the list of searchmodes supported by the attribute.
- *
- * Note that not all modes may be supported by the database driver.
- * Compare this list to the one returned by the databasedriver, to
- * determine which searchmodes may be used.
- *
- * @return array List of supported searchmodes
- */
- function getSearchModes()
- {
- if ($this->hasFlag(AF_LARGE) || $this->hasFlag(AF_MANYTOONE_AUTOCOMPLETE))
- {
- return array("substring","exact","wildcard","regex");
- }
- return array("exact"); // only support exact search when searching with dropdowns
- }
- /**
- * Creates a smart search condition for a given search value, and adds it
- * to the query that will be used for performing the actual search.
- *
- * @param Integer $id The unique smart search criterium identifier.
- * @param Integer $nr The element number in the path.
- * @param Array $path The remaining attribute path.
- * @param atkQuery $query The query to which the condition will be added.
- * @param String $ownerAlias The owner table alias to use.
- * @param Mixed $value The value the user has entered in the searchbox.
- * @param String $mode The searchmode to use.
- */
- function smartSearchCondition($id, $nr, $path, &$query, $ownerAlias, $value, $mode)
- {
- if (count($path) > 0)
- {
- $this->createDestination();
- $destAlias = "ss_{$id}_{$nr}_".$this->fieldName();
- $query->addJoin(
- $this->m_destInstance->m_table, $destAlias,
- $this->getJoinCondition($query, $ownerAlias, $destAlias),
- false
- );
- $attrName = array_shift($path);
- $attr = &$this->m_destInstance->getAttribute($attrName);
- if (is_object($attr))
- {
- $attr->smartSearchCondition($id, $nr + 1, $path, $query, $destAlias, $value, $mode);
- }
- }
- else
- {
- $this->searchCondition($query, $ownerAlias, $value, $mode);
- }
- }
- /**
- * Creates a searchcondition for the field,
- * was once part of searchCondition, however,
- * searchcondition() also immediately adds the search condition.
- *
- * @param atkQuery $query The query object where the search condition should be placed on
- * @param String $table The name of the table in which this attribute
- * is stored
- * @param mixed $value The value the user has entered in the searchbox
- * @param String $searchmode The searchmode to use. This can be any one
- * of the supported modes, as returned by this
- * attribute's getSearchModes() method.
- * @param string $fieldaliasprefix The prefix for the field
- * @return String The searchcondition to use.
- */
- function getSearchCondition(&$query, $table, $value, $searchmode, $fieldaliasprefix='')
- {
- if (!$this->createDestination()) return;
- if (is_array($value))
- {
- foreach ($this->m_listColumns as $attr)
- {
- $attrValue = $value[$attr];
- if (!empty($attrValue))
- {
- $p_attrib = &$this->m_destInstance->m_attribList[$attr];
- if (!$p_attrib == NULL)
- {
- $p_attrib->searchCondition($query, $this->fieldName(), $attrValue, $this->getChildSearchMode($searchmode, $p_attrib->formName()));
- }
- }
- }
- if (isset($value[$this->fieldName()]))
- {
- $value = $value[$this->fieldName()];
- }
- }
- if (empty($value))
- {
- return '';
- }
- else if (!$this->hasFlag(AF_LARGE) && !$this->hasFlag(AF_RELATION_AUTOCOMPLETE))
- {
- // We only support 'exact' matches.
- // But you can select more than one value, which we search using the IN() statement,
- // which should work in any ansi compatible database.
- if (!is_array($value)) // This last condition is for when the user selected the 'search all' option, in which case, we don't add conditions at all.
- {
- $value = array($value);
- }
- if (count($value)==1) // exactly one value
- {
- if ($value[0] == "__NONE__")
- {
- return $query->nullCondition($table.".".$this->fieldName(), true);
- }
- elseif ($value[0] != "")
- {
- return $query->exactCondition($table.".".$this->fieldName(),$this->escapeSQL($value[0]));
- }
- }
- else // search for more values using IN()
- {
- return $table.".".$this->fieldName()." IN ('".implode("','",$value)."')";
- }
- }
- else // AF_LARGE || AF_RELATION_AUTOCOMPLETE
- {
- // If we have a descriptor with multiple fields, use CONCAT
- $attribs = $this->m_destInstance->descriptorFields();
- $alias = $fieldaliasprefix . $this->fieldName();
- if(count($attribs)>1)
- {
- $searchcondition = $this->getConcatFilter($value,$alias);
- }
- else
- {
- // ask the destination node for it's search condition
- $searchcondition = $this->m_destInstance->getSearchCondition($query, $alias, $fieldaliasprefix, $value, $this->getChildSearchMode($searchmode, $this->formName()));
- }
- return $searchcondition;
- }
- }
- /**
- * Adds this attribute to database queries.
- *
- * Database queries (select, insert and update) are passed to this method
- * so the attribute can 'hook' itself into the query.
- *
- * @param atkQuery $query The SQL query object
- * @param String $tablename The name of the table of this attribute
- * @param String $fieldaliasprefix Prefix to use in front of the alias
- * in the query.
- * @param Array $rec The record that contains the value of this attribute.
- * @param int $level Recursion level if relations point to eachother, an
- * endless loop could occur if they keep loading
- * eachothers data. The $level is used to detect this
- * loop. If overriden in a derived class, any subcall to
- * an addToQuery method should pass the $level+1.
- * @param String $mode Indicates what kind of query is being processing:
- * This can be any action performed on a node (edit,
- * add, etc) Mind you that "add" and "update" are the
- * actions that store something in the database,
- * whereas the rest are probably select queries.
- */
- function addToQuery(&$query, $tablename="", $fieldaliasprefix="", $rec="", $level=0, $mode="")
- {
- if ($this->hasFlag(AF_MANYTOONE_LAZY))
- {
- parent::addToQuery($query, $tablename, $fieldaliasprefix, $rec, $level, $mode);
- return;
- }
- if ($this->createDestination())
- {
- if ($mode != "update" && $mode != "add")
- {
- $alias = $fieldaliasprefix . $this->fieldName();
- $query->addJoin($this->m_destInstance->m_table,
- $alias,
- $this->getJoinCondition($query, $tablename, $alias),
- $this->m_leftjoin);
- $this->m_destInstance->addToQuery($query, $alias, $level+1, false, $mode, $this->m_listColumns);
- }
- else
- {
- for ($i=0, $_i=count($this->m_refKey); $i<$_i; $i++)
- {
- if ($rec[$this->fieldName()]===NULL)
- {
- $query->addField($this->m_refKey[$i],"NULL","","",false);
- }
- else
- {
- $value = $rec[$this->fieldName()];
- if (is_array($value))
- {
- $fk = &$this->m_destInstance->getAttribute($this->m_destInstance->m_primaryKey[$i]);
- $value = $fk->value2db($value);
- }
- $query->addField($this->m_refKey[$i],$value,"","",!$this->hasFlag(AF_NO_QUOTES));
- }
- }
- }
- }
- }
- /**
- * Retrieve detail records from the database.
- *
- * Called by the framework to load the detail records.
- *
- * @param atkDb $db The database used by the node.
- * @param array $record The master record
- * @param String $mode The mode for loading (admin, select, copy, etc)
- *
- * @return array Recordset containing detailrecords, or NULL if no detail
- * records are present. Note: when $mode is edit, this
- * method will always return NULL. This is a framework
- * optimization because in edit pages, the records are
- * loaded on the fly.
- */
- function load(&$db, $record, $mode)
- {
- return $this->_getSelectedRecord($record, $mode);
- }
- /**
- * Determine the load type of this attribute.
- *
- * With this method, the attribute tells the framework whether it wants
- * to be loaded in the main query (addToQuery) or whether the attribute
- * has its own load() implementation.
- * For the atkOneToOneRelation, this depends on the presence of the
- * AF_ONETOONE_LAZY flag.
- *
- * Framework method. It should not be necesary to call this method
- * directly.
- *
- * @param String $mode The type of load (view,admin,edit etc)
- *
- * @return int Bitmask containing information about load requirements.
- * POSTLOAD|ADDTOQUERY when AF_ONETOONE_LAZY is set.
- * ADDTOQUERY when AF_ONETOONE_LAZY is not set.
- */
- function loadType($mode)
- {
- if (isset($this->m_loadType[$mode]) && $this->m_loadType[$mode] !== null)
- return $this->m_loadType[$mode];
- else if (isset($this->m_loadType[null]) && $this->m_loadType[null] !== null)
- return $this->m_loadType[null];
- // Default backwardscompatible behaviour:
- else if ($this->hasFlag(AF_MANYTOONE_LAZY))
- return POSTLOAD|ADDTOQUERY;
- else
- return ADDTOQUERY;
- }
- /**
- * Validate if the record we are referring to really exists.
- *
- * @param array $record
- * @param string $mode
- */
- function validate(&$record, $mode)
- {
- $sessionmanager = atkGetSessionManager();
- if ($sessionmanager) $storetype = $sessionmanager->stackVar('atkstore');
- if ($storetype!=='session' && !$this->_isSelectableRecord($record))
- {
- triggerError($record, $this->fieldName(), 'error_integrity_violation');
- }
- }
- /**
- * Check if two records have the same value for this attribute
- *
- * @param array $recA Record A
- * @param array $recB Record B
- * @return boolean to indicate if the records are equal
- */
- function equal($recA, $recB)
- {
- if ($this->createDestination())
- {
- return (($recA[$this->fieldName()][$this->m_destInstance->primaryKeyField()]
- ==
- $recB[$this->fieldName()][$this->m_destInstance->primaryKeyField()])
- ||
- ($this->isEmpty($recA)&&$this->isEmpty($recB)));
- // we must also check empty values, because empty values need not necessarily
- // be equal (can be "", NULL or 0.
- }
- return false;
- }
- /**
- * Return the database field type of the attribute.
- *
- * Note that the type returned is a 'generic' type. Each database
- * vendor might have his own types, therefor, the type should be
- * converted to a database specific type using $db->fieldType().
- *
- * If the type was read from the table metadata, that value will
- * be used. Else, th…
Large files files are truncated, but you can click here to view the full file