/library/Adapto/Relation/ManyToOne.php
PHP | 1683 lines | 901 code | 160 blank | 622 comment | 195 complexity | 34915e09a3c8ef37093933492be42597 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- <?php
- /**
- * This file is part of the Adapto Toolkit.
- * Detailed copyright and licensing information can be found
- * in the doc/COPYRIGHT and doc/LICENSE files which should be
- * included in the distribution.
- *
- * @package adapto
- * @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
- *
- */
- /**
- * 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 ijansch
- * @package adapto
- * @subpackage relations
- *
- */
- class Adapto_Relation_ManyToOne extends Adapto_Relation
- {
- 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
- */
- public $m_leftjoin = true; // defaulted to public
- /**
- * The array of referential key fields.
- * @access private
- * @var array
- */
- public $m_refKey = array(); // defaulted to public
- /**
- * SQL statement with extra filter for the join that retrieves the
- * selected record.
- * @var String
- */
- public $m_joinFilter = ""; // defaulted to public
- /**
- * Hide the relation when there are no records to select.
- * @access private
- * @var boolean
- */
- public $m_hidewhenempty = false; // defaulted to public
- /**
- * List columns.
- * @access private
- * @var Array
- */
- public $m_listColumns = array(); // defaulted to public
- /**
- * Always show list columns?
- * @access private
- * @var boolean
- */
- public $m_alwaysShowListColumns = false; // defaulted to public
- /**
- * Label to use for the 'none' option.
- *
- * @access private
- * @var String
- */
- public $m_noneLabel = NULL; // defaulted to public
- /**
- * Minimum number of character a user needs to enter before auto-completion kicks in.
- *
- * @access private
- * @var int
- */
- public $m_autocomplete_minchars = 2; // defaulted to public
- /**
- * An array with the fieldnames of the destination entity in which the autocompletion must search
- * for results.
- *
- * @access private
- * @var array
- */
- public $m_autocomplete_searchfields = ""; // defaulted to public
- /**
- * The search mode of the autocomplete fields. Can be 'startswith', 'exact' or 'contains'.
- *
- * @access private
- * @var String
- */
- public $m_autocomplete_searchmode = "contains"; // defaulted to public
- /**
- * Value determines wether the search of the autocompletion is case-sensitive.
- *
- * @var boolean
- */
- public $m_autocomplete_search_case_sensitive = false; // defaulted to public
- /**
- * Value determines if select link for autocomplete should use atkSubmit or not (for use in admin screen for example)
- *
- * @var boolean
- */
- public $m_autocomplete_saveform = true; // defaulted to public
- /**
- * Set the minimal number of records for showing the automcomplete. If there are less records
- * the normal dropdown is shown
- *
- * @access private
- * @var integer
- */
- public $m_autocomplete_minrecords = -1; // defaulted to public
- /**
- * Set the size attribute of the autocompletion input element
- *
- * @access protected
- * @var integer
- */
- protected $m_autocomplete_size = 30;
- /**
- * Destination entity for auto links (edit, new)
- *
- * @var string
- */
- protected $m_autolink_destination = "";
- // override onchangehandler init
- public $m_onchangehandler_init = "newvalue = el.options[el.selectedIndex].value;\n"; // defaulted to public
- /**
- * Use destination filter for autolink add link?
- *
- * @access private
- * @var boolean
- */
- public $m_useFilterForAddLink = true; // defaulted to public
- /**
- * Set a function to use for determining the descriptor in the getConcatFilter function
- *
- * @access private
- * @var string
- */
- public $m_concatDescriptorFunction = ''; // defaulted to public
- /**
- * 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 Adapto_Relation_ManyToOne::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 entity.
- * @param String $destination The entity we have a relationship with.
- * @param int $flags Flags for the relation
- */
- public function __construct($name, $destination, $flags = 0)
- {
- if (Adapto_Config::getGlobal("manytoone_autocomplete_default", false))
- $flags |= AF_RELATION_AUTOCOMPLETE;
- if (Adapto_Config::getGlobal("manytoone_autocomplete_large", true) && hasFlag($flags, AF_LARGE))
- $flags |= AF_RELATION_AUTOCOMPLETE;
- $this->m_autocomplete_minchars = Adapto_Config::getGlobal("manytoone_autocomplete_minchars", 2);
- $this->m_autocomplete_searchmode = Adapto_Config::getGlobal("manytoone_autocomplete_searchmode", "contains");
- $this->m_autocomplete_search_case_sensitive = Adapto_Config::getGlobal("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.
- parent::__construct($name[0], $destination, $flags);
- } else {
- $this->m_refKey[] = $name;
- parent::__construct($name, $destination, $flags);
- }
- if ($this->hasFlag(AF_MANYTOONE_LAZY) && (count($this->m_refKey) > 1 || $this->m_refKey[0] != $this->fieldName())) {
- throw new Adapto_Exception("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 Adapto_Relation_ManyToOne The instance of this Adapto_Relation_ManyToOne
- */
- function addFlag($flag)
- {
- parent::addFlag($flag);
- if (Adapto_Config::getGlobal("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;
- }
- /**
- * Set the size of the rendered autocompletion input element
- *
- * @param integer $size
- */
- function setAutoCompleteSize($size)
- {
- $this->m_autocomplete_size = $size;
- }
- /**
- * 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 entity
- *
- * @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 entity
- * that (only) will be displayed in the recordlist.
- *
- * @param string $attr The attribute to add to the listcolumn
- * @return Adapto_Relation_ManyToOne The instance of this Adapto_Relation_ManyToOne
- */
- function addListColumn($attr)
- {
- $this->m_listColumns[] = $attr;
- return $this;
- }
- /**
- * Add multiple list columns. Attributes of the destination entity
- * that (only) will be displayed in the recordlist.
- * @return Adapto_Relation_ManyToOne The instance of this Adapto_Relation_ManyToOne
- */
- 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 entity that (only) will be displayed in the recordlist.
- * @return Adapto_Relation_ManyToOne The instance of this Adapto_Relation_ManyToOne
- */
- 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 Adapto_Relation_ManyToOne The instance of this Adapto_Relation_ManyToOne
- */
- 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)) {
- Adapto_Util_Debugger::debug($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;
- $entityname = $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, $entityname);
- 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(EF_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 {
- Adapto_Util_Debugger::debug("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;
- Adapto_Util_Debugger::debug("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 entity for the Autolink links (new/edit)
- *
- * @param string $entity
- */
- function setAutoLinkDestination($entity)
- {
- $this->m_autolink_destination = $entity;
- }
- /**
- * Get destination entity 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 && !$this->getConfigOptionObligatoryNullOption()
- && (($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 the configuration option called: list_obligatory_null_item, which is a
- * boolean.
- * @return boolean
- */
- public function getConfigOptionObligatoryNullOption()
- {
- return Adapto_Config::getGlobal("list_obligatory_null_item");
- }
- /**
- * 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 throw new Adapto_Exception("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))
- || ($this->getConfigOptionObligatoryNullOption() && !is_array($value))) {
- $result .= '<option value="">' . $this->getNoneLabel() . '</option>';
- }
- foreach ($recordset as $selectable) {
- $pk = $this->getDestination()->primaryKey($selectable);
- $sel = $pk == $currentPk ? 'selected="selected"' : '';
- $result .= '<option value="' . $pk . '" ' . $sel . '>'
- . str_replace(' ', ' ', Adapto_htmlentities(strip_tags($this->m_destInstance->descriptor($selectable)))) . '</option>';
- }
- $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 entity
- *
- * @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_" . getEntityType($this->m_destination), $this->getOwnerInstance()->getModule(), $this->getOwnerInstance()->getType(),
- '', '', true);
- if (!$linkname)
- $linkname = atktext("link_select_" . getEntityType($this->m_destination), getEntityModule($this->m_destination), getEntityType($this->m_destination), '',
- '', true);
- if (!$linkname)
- $linkname = atktext("select_a") . ' '
- . strtolower(atktext(getEntityType($this->m_destination), getEntityModule($this->m_destination), getEntityType($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 entity
- * @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(Adapto_Config::getGlobal("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 entity.
- * (<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 = Adapto_Config::getGlobal("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, 'Adapto_Relation_ManyToOne') && 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()]) && Adapto_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()]) && Adapto_in_array($pk, $record[$this->fieldName()]))
- $sel = "selected";
- else
- $sel = "";
- $result .= '<option value="' . $pk . '" ' . $sel . '>'
- . str_replace(' ', ' ', Adapto_htmlentities(strip_tags($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->atkEntityType(), $this->m_ownerInstance->m_action,
- 'attribute.' . $this->fieldName() . '.autocomplete_search');
- $code = "Adapto.ManyToOneRelation.completeSearch('{$id}', '{$id}_result', '{$url}', {$this->m_autocomplete_minchars});";
- $this->m_ownerInstance->addStyle("atkmanytoonerelation.css");
- $page->register_script(Adapto_Config::getGlobal('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 entity 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 quer…
Large files files are truncated, but you can click here to view the full file