/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
- <?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, the attribute will analyze its flags to guess
- * what type it should be. If AF_AUTO_INCREMENT is set, the field
- * is probaly "number". If not, it's probably "string".
- *
- * @return String The 'generic' type of the database field for this
- * attribute.
- */
- function dbFieldType()
- {
- // The type of field that we need to store the foreign key, is equal to
- // the type of field of the primary key of the node we have a
- // relationship with.
- if ($this->createDestination())
- {
- if(count($this->m_refKey)>1)
- {
- $keys = array();
- for($i=0, $_i=count($this->m_refKey); $i<$_i; $i++)
- {
- $keys [] = $this->m_destInstance->m_attribList[$this->m_destInstance->m_primaryKey[$i]]->dbFieldType();
- }
- return $keys;
- }
- else
- return $this->m_destInstance->m_attribList[$this->m_destInstance->primaryKeyField()]->dbFieldType();
- }
- return "";
- }
- /**
- * Return the size of the field in the database.
- *
- * If 0 is returned, the size is unknown. In this case, the
- * return value should not be used to create table columns.
- *
- * Ofcourse, the size does not make sense for every field type.
- * So only interpret the result if a size has meaning for
- * the field type of this attribute. (For example, if the
- * database field is of type 'date', the size has no meaning)
- *
- * @return int The database field size
- */
- function dbFieldSize()
- {
- // The size of the field we need to store the foreign key, is equal to
- // the size of the field of the primary key of the node we have a
- // relationship with.
- if ($this->createDestination())
- {
- if(count($this->m_refKey)>1)
- {
- $keys = array();
- for($i=0, $_i=count($this->m_refKey); $i<$_i; $i++)
- {
- $keys [] = $this->m_destInstance->m_attribList[$this->m_destInstance->m_primaryKey[$i]]->dbFieldSize();
- }
- return $keys;
- }
- else
- return $this->m_destInstance->m_attribList[$this->m_destInstance->primaryKeyField()]->dbFieldSize();
- }
- return 0;
- }
- /**
- * Returns the selected record for this many-to-one relation. Uses
- * the owner instance $this->fieldName()."_selected" method if it exists.
- *
- * @param array $record The record
- * @param string $mode The mode we're in
- * @return Array with the selected record
- */
- function _getSelectedRecord($record=array(), $mode="select")
- {
- $method = $this->fieldName()."_selected";
- if (method_exists($this->m_ownerInstance, $method))
- return $this->m_ownerInstance->$method($record, $mode);
- else return $this->getSelectedRecord($record, $mode);
- }
- /**
- * Returns the currently selected record.
- *
- * @param array $record The record
- * @param string $mode The mode we're in
- * @return Array with the selected record
- */
- function getSelectedRecord($record=array(), $mode="select")
- {
- $this->createDestination();
- $condition = $this->m_destInstance->m_table.'.'.$this->m_destInstance->primaryKeyField().
- "='".$record[$this->fieldName()][$this->m_destInstance->primaryKeyField()]."'";
- $filter = $this->createFilter($record);
- if (!empty($filter))
- {
- $condition = $condition.' AND '.$filter;
- }
- $record = $this->m_destInstance->select($condition)->mode($mode)->firstRow();
- return $record;
- }
- /**
- * Returns the selectable records for this many-to-one relation. Uses
- * the owner instance $this->fieldName()."_selection" method if it exists.
- *
- * @param array $record The record
- * @param string $mode The mode we're in
- * @return Array with the selectable records
- */
- function _getSelectableRecords($record=array(), $mode="select")
- {
- $method = $this->fieldName()."_selection";
- if (method_exists($this->m_ownerInstance, $method))
- return $this->m_ownerInstance->$method($record, $mode);
- else return $this->getSelectableRecords($record, $mode);
- }
- /**
- * Is selectable record? Uses the owner instance $this->fieldName()."_selectable"
- * method if it exists.
- *
- * @param array $record The record
- * @param string $mode The mode we're in
- * @return Boolean to indicate if the record is selectable
- */
- function _isSelectableRecord($record=array(), $mode="select")
- {
- $method = $this->fieldName()."_selectable";
- if (method_exists($this->m_ownerInstance, $method))
- return $this->m_ownerInstance->$method($record, $mode);
- else return $this->isSelectableRecord($record, $mode);
- }
- /**
- * Create the destination filter for the given record.
- *
- * @param array $record
- * @return string filter
- */
- function createFilter($record)
- {
- if ($this->m_destinationFilter != "")
- {
- atkimport("atk.utils.atkstringparser");
- $parser = new atkStringParser($this->m_destinationFilter);
- return $parser->parse($record);
- }
- else
- {
- return "";
- }
- }
- /**
- * Is selectable record?
- *
- * Use this one from your selectable override when needed.
- *
- * @param array $record The record
- * @param string $mode The mode we're in
- * @return Boolean to indicate if the record is selectable
- */
- function isSelectableRecord($record=array(), $mode="select")
- {
- if ($record[$this->fieldName()] == NULL) return false;
- $this->createDestination();
- // if the value is set directly in the record field we first
- // need to convert the value to an array
- if (!is_array($record[$this->fieldName()]))
- {
- $record[$this->fieldName()] = array(
- $this->m_destInstance->primaryKeyField() => $record[$this->fieldName()]
- );
- }
- $selectedKey = $this->m_destInstance->primaryKey($record[$this->fieldName()]);
- if ($selectedKey == NULL) return false;
- // If custom selection method exists we use this one, although this is
- // way more inefficient, so if you create a selection override you should
- // also think about creating a selectable override!
- $method = $this->fieldName()."_selection";
- if (method_exists($this->m_ownerInstance, $method))
- {
- $rows = $this->m_ownerInstance->$method($record, $mode);
- foreach ($rows as $row)
- {
- $key = $this->m_destInstance->primaryKey($row);
- if ($key == $selectedKey) return true;
- }
- return false;
- }
- // No selection override exists, simply add the record key to the selector.
- $filter = $this->createFilter($record);
- $selector = "($selectedKey)".($filter != NULL ? " AND ($filter)" : "");
-
- return $this->m_destInstance->select($selector)->getRowCount() > 0;
- }
- /**
- * Returns the selectable records.
- *
- * Use this one from your selection override when needed.
- *
- * @param array $record The record
- * @param string $mode The mode we're in
- * @return Array with the selectable records
- */
- function getSelectableRecords($record=array(), $mode="select")
- {
- $this->createDestination();
- $selector = $this->createFilter($record);
- $result =
- $this->m_destInstance
- ->select($selector)
- ->orderBy($this->getDestination()->getOrder())
- ->includes(atk_array_merge($this->m_destInstance->descriptorFields(),$this->m_destInstance->m_primaryKey))
- ->mode($mode)
- ->allRows();
- return $result;
- }
- /**
- * Returns the condition (SQL) that should be used when we want to join a relation's
- * owner node with the parent node.
- *
- * @param atkQuery $query The query object
- * @param String $tablename The tablename on which to join
- * @param String $fieldalias The fieldalias
- * @return String SQL string for joining the owner with the destination.
- * Returns false when impossible (f.e. attrib is not a relation).
- */
- function getJoinCondition(&$query, $tablename="",$fieldalias="")
- {
- if (!$this->createDestination()) return false;
- if ($tablename!="") $realtablename=$tablename;
- else $realtablename = $this->m_ownerInstance->m_table;
- $joinconditions = array();
- for ($i=0, $_i=count($this->m_refKey); $i<$_i; $i++)
- {
- $joinconditions[] = $realtablename.".".$this->m_refKey[$i].
- "=".
- $fieldalias.".".$this->m_destInstance->m_primaryKey[$i];
- }
- if ($this->m_joinFilter!="")
- {
- atkimport('atk.utils.atkstringparser');
- $parser = new atkStringParser($this->m_joinFilter);
- $filter = $parser->parse(array('table' => $realtablename, 'owner' => $realtablename, 'destination' => $fieldalias));
- $joinconditions[] = $filter;
- }
- return implode(" AND ",$joinconditions);
- }
- /**
- * Make this relation hide itself from the form when there are no items to select
- *
- * @param boolean $hidewhenempty true - hide when empty, false - always show
- */
- function setHideWhenEmpty($hidewhenempty)
- {
- $this->m_hidewhenempty = $hidewhenempty;
- }
- /**
- * Adds the attribute's edit / hide HTML code to the edit array.
- *
- * This method is called by the node if it wants the data needed to create
- * an edit form.
- *
- * This is a framework method, it should never be called directly.
- *
- * @param String $mode the edit mode ("add" or "edit")
- * @param array $arr pointer to the edit array
- * @param array $defaults pointer to the default values array
- * @param array $error pointer to the error array
- * @param String $fieldprefix the fieldprefix
- */
- function addToEditArray($mode, &$arr, &$defaults, &$error, $fieldprefix)
- {
- if ($this->createDestination())
- {
- // check if destination table is empty
- // only check if hidewhenempty is set to true
- if ($this->m_hidewhenempty)
- {
- $recs = $this->_getSelectableRecords($defaults, 'select');
- if (count($recs)==0) return $this->hide($defaults, $fieldprefix);
- }
- }
- return parent::addToEditArray($mode, $arr, $defaults, $error, $fieldprefix);
- }
- /**
- * Retrieves the ORDER BY statement for the relation.
- *
- * @param Array $extra A list of attribute names to add to the order by
- * statement
- * @param String $table The table name (if not given uses the owner node's table name)
- * @param String $direction Sorting direction (ASC or DESC)
- * @return String The ORDER BY statement for this attribute
- */
- function getOrderByStatement($extra='', $table='', $direction='ASC')
- {
- if (!$this->createDestination())
- return parent::getOrderByStatement();
- if (!empty($table))
- {
- $table = $table.'_AE_'.$this->fieldName();
- }
- else
- {
- $table = $this->fieldName();
- }
- if (!empty($extra) && in_array($extra, $this->m_listColumns))
- {
- return $this->getDestination()->getAttribute($extra)->getOrderByStatement('', $table, $direction);
- }
- $order = $this->m_destInstance->getOrder();
- if (!empty($order))
- {
- $newParts = array();
- $parts = explode(',', $order);
- foreach ($parts as $part)
- {
- $split = preg_split('/\s+/', trim($part));
- $field = isset($split[0]) ? $split[0] : null;
- $fieldDirection = empty($split[1]) ? 'ASC' : strtoupper($split[1]);
- // if our default direction is DESC (the opposite of the default ASC)
- // we always have to switch the given direction to be the opposite, e.g.
- // DESC => ASC and ASC => DESC, this way we respect the default ordering
- // in the destination node even if the default is descending
- if ($fieldDirection == 'DESC')
- {
- $fieldDirection = $direction == 'DESC' ? 'ASC' : 'DESC';
- }
- else
- {
- $fieldDirection = $direction;
- }
- if (strpos($field, '.') !== false)
- {
- list(,$field) = explode('.', $field);
- }
- $newPart = $this->getDestination()->getAttribute($field)->getOrderByStatement('', $table, $fieldDirection);
- // realias if destination order contains the wrong tablename.
- if (strpos($newPart, $this->m_destInstance->m_table.'.') !== false)
- {
- $newPart= str_replace($this->m_destInstance->m_table.'.', $table.'.', $newPart);
- }
- $newParts[] = $newPart;
- }
- return implode(', ', $newParts);
- }
- else
- {
- $fields = $this->m_destInstance->descriptorFields();
- if (count($fields) == 0)
- $fields = array($this->m_destInstance->primaryKeyField());
- $order = "";
- foreach ($fields as $field)
- $order .= (empty($order) ? '' : ', ').$table.".".$field;
- return $order;
- }
- }
- /**
- * Adds the attribute / field to the list header. This includes the column name and search field.
- *
- * Framework method. It should not be necessary to call this method directly.
- *
- * @param String $action the action that is being performed on the node
- * @param array $arr reference to the the recordlist array
- * @param String $fieldprefix the fieldprefix
- * @param int $flags the recordlist flags
- * @param array $atksearch the current ATK search list (if not empty)
- * @param atkColumnConfig $columnConfig Column configuration object
- * @param atkDataGrid $grid The atkDataGrid this attribute lives on.
- * @param string $column child column (null for this attribute, * for this attribute and all childs)
- */
- public function addToListArrayHeader($action, &$arr, $fieldprefix, $flags, $atksearch, $atkorderby, atkDataGrid $grid=null, $column='*')
- {
- if ($column == null || $column == '*')
- {
- $prefix = $fieldprefix.$this->fieldName()."_AE_";
- parent::addToListArrayHeader($action, $arr, $prefix, $flags, $atksearch[$this->fieldName()], $atkorderby, $grid, null);
- }
-
- if ($column == '*')
- {
- // only add extra columns when needed
- if ($this->hasFlag(AF_HIDE_LIST) && !$this->m_alwaysShowListColumns) return;
- if (!$this->createDestination() || count($this->m_listColumns) == 0) return;
- foreach ($this->m_listColumns as $column)
- {
- $this->_addColumnToListArrayHeader($column, $action, $arr, $fieldprefix, $flags, $atksearch, $atkorderby, $grid);
- }
- }
- else if ($column != null)
- {
- $this->_addColumnToListArrayHeader($column, $action, $arr, $fieldprefix, $flags, $atksearch, $atkorderby, $grid);
- }
- }
-
- /**
- * Adds the child attribute / field to the list row.
- *
- * Framework method. It should not be necessary to call this method directly.
- *
- * @param string $column child column (null for this attribute, * for this attribute and all childs)
- * @param String $action the action that is being performed on the node
- * @param array $arr reference to the the recordlist array
- * @param String $fieldprefix the fieldprefix
- * @param int $flags the recordlist flags
- * @param array $atksearch the current ATK search list (if not empty)
- * @param string $atkorderby order by
- * @param atkDataGrid $grid The atkDataGrid this attribute lives on.
- */
- protected function _addColumnToListArrayHeader($column, $action, &$arr, $fieldprefix, $flags, $atksearch, $atkorderby, atkDataGrid $grid=null)
- {
- $prefix = $fieldprefix.$this->fieldName()."_AE_";
- $p_attrib = $this->m_destInstance->getAttribute($column);
- if ($p_attrib == null)
- {
- throw new Exception("Invalid list column {$column} for atkManyToOneRelation ".$this->getOwnerInstance()->atkNodeType().'::'.$this->fieldName());
- }
-
- $p_attrib->m_flags |= AF_HIDE_LIST;
- $p_attrib->m_flags ^= AF_HIDE_LIST;
- $p_attrib->addToListArrayHeader($action, $arr, $prefix, $flags, $atksearch[$this->fieldName()], $atkorderby, $grid, null);
- // fix order by clause
- $needle = $prefix.$column;
- foreach (array_keys($arr['heading']) as $key)
- {
- if (strpos($key, $needle) !== 0) continue;
-
- $order = $arr['heading'][$key]['order'];
- if (empty($order)) continue;
-
- $order = $this->fieldName().'.'.$order;
- if (is_object($atkorderby) &&
- isset($atkorderby->m_colcfg[$this->fieldName()])
- && isset($atkorderby->m_colcfg[$this->fieldName()]['extra'])
- && $atkorderby->m_colcfg[$this->fieldName()]['extra'] == $column)
- {
- $direction = $atkorderby->getDirection($this->fieldName());
- if ($direction=="asc") $order.=" desc";
- }
- $arr['heading'][$key]['order'] = $order;
- }
- }
- /**
- * Adds the attribute / field to the list row. And if the row is totalisable also to the total.
- *
- * Framework method. It should not be necessary to call this method directly.
- *
- * @param String $action the action that is being performed on the node
- * @param array $arr reference to the the recordlist array
- * @param int $nr the current row number
- * @param String $fieldprefix the fieldprefix
- * @param int $flags the recordlist flags
- * @param boolean $edit editing?
- * @param atkDataGrid $grid data grid
- * @param string $column child column (null for this attribute, * for this attribute and all childs)
- */
- public function addToListArrayRow($action, &$arr, $nr, $fieldprefix, $flags, $edit=false, atkDataGrid $grid=null, $column='*')
- {
- if ($column == null || $column == '*')
- {
- $prefix = $fieldprefix.$this->fieldName()."_AE_";
- parent::addToListArrayRow($action, $arr, $nr, $prefix, $flags, $edit, $grid, null);
- }
-
- if ($column == '*')
- {
- // only add extra columns when needed
- if ($this->hasFlag(AF_HIDE_LIST) && !$this->m_alwaysShowListColumns) return;
- if (!$this->createDestination() || count($this->m_listColumns) == 0) return;
- foreach ($this->m_listColumns as $column)
- {
- $this->_addColumnToListArrayRow($column, $action, $arr, $nr, $fieldprefix, $flags, $edit, $grid);
- }
- }
- else if ($column != null)
- {
- $this->_addColumnToListArrayRow($column, $action, $arr, $nr, $fieldprefix, $flags, $edit, $grid);
- }
- }
- /**
- * Adds the child attribute / field to the list row.
- *
- * @param string $column child attribute name
- * @param String $action the action that is being performed on the node
- * @param array $arr reference to the the recordlist array
- * @param int $nr the current row number
- * @param String $fieldprefix the fieldprefix
- * @param int $flags the recordlist flags
- * @param boolean $edit editing?
- * @param atkDataGrid $grid data grid
- */
- protected function _addColumnToListArrayRow($column, $action, &$arr, $nr, $fieldprefix, $flags, $edit=false, atkDataGrid $grid=null)
- {
- $prefix = $fieldprefix.$this->fieldName()."_AE_";
- // small trick, the destination record is in a subarray. The destination
- // addToListArrayRow will not expect this though, so we have to modify the
- // record a bit before passing it to the detail columns.
- $backup = $arr["rows"][$nr]["record"];
- $arr["rows"][$nr]["record"] = $arr["rows"][$nr]["record"][$this->fieldName()];
- $p_attrib = $this->m_destInstance->getAttribute($column);
- if ($p_attrib == null)
- {
- throw new Exception("Invalid list column {$column} for atkManyToOneRelation ".$this->getOwnerInstance()->atkNodeType().'::'.$this->fieldName());
- }
- $p_attrib->m_flags |= AF_HIDE_LIST;
- $p_attrib->m_flags ^= AF_HIDE_LIST;
- $p_attrib->addToListArrayRow($action, $arr, $nr, $prefix, $flags, $edit, $grid, null);
- $arr["rows"][$nr]["record"] = $backup;
- }
- /**
- * Adds the needed searchbox(es) for this attribute to the fields array. This
- * method should only be called by the atkSearchHandler.
- * Overridden method; in the integrated version, we should let the destination
- * attributes hook themselves into the fieldlist instead of hooking the relation
- * in it.
- *
- * @param array $fields The array containing fields to use in the
- * extended search
- * @param atkNode $node The node where the field is in
- * @param array $record A record containing default values to put
- * into the search fields.
- * @param array $fieldprefix search / mode field prefix
- */
- function addToSearchformFields(&$fields, &$node, &$record, $fieldprefix = "")
- {
- $prefix = $fieldprefix.$this->fieldName()."_AE_";
- parent::addToSearchformFields($fields, $node, $record, $prefix);
- // only add extra columns when needed
- if ($this->hasFlag(AF_HIDE_LIST) && !$this->m_alwaysShowListColumns) return;
- if (!$this->createDestination() || count($this->m_listColumns) == 0) return;
- foreach ($this->m_listColumns as $attribname)
- {
- $p_attrib = &$this->m_destInstance->m_attribList[$attribname];
- $p_attrib->m_flags |= AF_HIDE_LIST;
- $p_attrib->m_flags ^= AF_HIDE_LIST;
- if (!$p_attrib->hasFlag(AF_HIDE_SEARCH))
- {
- $p_attrib->addToSearchformFields($fields,$node,$record[$this->fieldName()], $prefix);
- }
- }
- }
- /**
- * Retrieve the sortorder for the listheader based on the
- * atkColumnConfig
- *
- * @param atkColumnConfig $columnConfig The config that contains options for
- * extended sorting and grouping to a
- * recordlist.
- * @return String Returns sort order ASC or DESC
- */
- function listHeaderSortOrder(&$columnConfig)
- {
- $order = $this->fieldName();
- // only add desc if not one of the listColumns is used for the sorting
- if (isset($columnConfig->m_colcfg[$order]) && empty($columnConfig->m_colcfg[$order]['extra']))
- {
- $direction = $columnConfig->getDirection($order);
- if ($direction=="asc") $order.=" desc";
- }
- return $order;
- }
- /**
- * Creates and registers the on change handler caller function.
- * This method will be used to message listeners for a change
- * event as soon as a new value is selected.
- *
- * @param string $fieldId
- * @param string $fieldPrefix
- * @param string $none
- * @return String function name
- */
- function createOnChangeCaller($fieldId, $fieldPrefix, $none='null')
- {
- $function = $none;
- if (count($this->m_onchangecode) > 0)
- {
- $function = "{$fieldId}_callChangeHandler";
- $js = "
- function {$function}() {
- {$fieldId}_onChange(\$('{$fieldId}'));
- }
- ";
- $this->m_onchangehandler_init = "newvalue = el.value;\n";
- $page = &$this->m_ownerInstance->getPage();
- $page->register_scriptcode($js);
- $this->_renderChangeHandler($fieldPrefix);
- }
- return $function;
- }
- /**
- * Draw the auto-complete box.
- *
- * @param array $record The record
- * @param string $fieldPrefix The fieldprefix
- * @param string $mode The mode we're in
- */
- function drawAutoCompleteBox($record, $fieldPrefix, $mode)
- {
- $this->createDestination();
- // register base JavaScript code and stylesheet
- $page = &$this->m_ownerInstance->getPage();
- $page->register_script(atkconfig('atkroot').'atk/javascript/class.atkmanytoonerelation.js');
- $this->m_ownerInstance->addStyle("atkmanytoonerelation.css");
- $id = $this->getHtmlId($fieldPrefix);
- // validate is this is a selectable record and if so
- // retrieve the display label and hidden value
- if ($this->_isSelectableRecord($record, 'select'))
- {
- $current = $record[$this->fieldName()];
- $label = $this->m_destInstance->descriptor($record[$this->fieldName()]);
- $value = $this->m_destInstance->primaryKey($record[$this->fieldName()]);
- }
- else
- {
- $current = NULL;
- $label = '';
- $value = '';
- }
- // create the widget
- $links = $this->createSelectAndAutoLinks($id, $record);
- $result =
- '<input type="hidden" id="'.$id.'" name="'.$id.'" value="'.$value.'" />
- <input type="text" id="'.$id.'_search" value="'.atk_htmlentities($label).'" class="atkmanytoonerelation_search" size="30" onfocus="this.select()" />
- <img id="'.$id.'_spinner" src="atk/images/spinner.gif" style="vertical-align: middle; display: none"> '.$links.'
- <div id="'.$id.'_result" style="display: none" class="atkmanytoonerelation_result"></div>';
- // register JavaScript code that attaches the auto-complete behaviour to the search box
- $url = partial_url($this->m_ownerInstance->atkNodeType(), $mode, 'attribute.'.$this->fieldName().'.autocomplete');
- $function = $this->createOnChangeCaller($id, $fieldPrefix);
- $code = "ATK.ManyToOneRelation.completeEdit('{$id}_search', '{$id}_result', '$id', '{$id}_spinner', '$url', $function, 1);";
- $page->register_loadscript($code);
- return $result;
- }
- /**
- * Auto-complete partial.
- *
- * @param string $mode add/edit mode?
- */
- function partial_autocomplete($mode)
- {
- $searchvalue = $this->m_ownerInstance->m_postvars['value'];
- if (atk_strlen($searchvalue) < $this->m_autocomplete_minchars)
- {
- return '<ul><li class="minimum_chars">'.sprintf($this->text('autocomplete_minimum_chars'), $this->m_autocomplete_minchars).'</li></ul>';
- }
- $this->createDestination();
- $fieldprefix = (isset($this->m_ownerInstance->m_postvars['atkfieldprefix'])?$this->m_ownerInstance->m_postvars['atkfieldprefix']:"");
- $searchvalue = $this->escapeSQL($searchvalue);
- $record = $this->m_ownerInstance->updateRecord();
- $filter = $this->createSearchFilter($searchvalue);
- $this->addDestinationFilter($filter);
- $records = $this->_getSelectableRecords($record, 'select');
- if (count($records) == 0)
- {
- if(in_array($this->m_autocomplete_searchmode,array("exact","startswith","contains")))
- $str = $this->text('autocomplete_no_results_'.$this->m_autocomplete_searchmode);
- else
- $str = $this->text('autocomplete_no_results');
- return '<ul><li class="no_results">'.$str.'</li></ul>';
- }
- $result = '';
- foreach ($records as $rec)
- {
- $option = atk_htmlentities($this->m_destInstance->descriptor($rec));
- $value = $this->m_destInstance->primaryKey($rec);
- $highlightedOption = $this->highlight_search_result_match($searchvalue, $option);
- $result .= '
- <li title="'.$option.'">
- '.$highlightedOption.'
- <span class="selection" style="display: none">'.$option.'</span>
- <span class="value" style="display: none">'.$value.'</span>
- </li>';
- }
- return "<ul>$result</ul>";
- }
- function highlight_search_result_match($search_value, $result)
- {
- $escaped_searchvalue = str_replace('/', '\/', preg_quote($search_value));
- return preg_replace('/('.$escaped_searchvalue.')/i', '<span class="atkmanytoone_highlite">\\1</span>', $result);
- }
- /**
- * Auto-complete search partial.
- *
- * @return HTML code with autocomplete result
- */
- function partial_autocomplete_search()
- {
- $this->createDestination();
- $searchvalue = $this->m_ownerInstance->m_postvars['value'];
- $searchvalue = $this->escapeSQL($searchvalue);
- $filter = $this->createSearchFilter($searchvalue);
- $this->addDestinationFilter($filter);
- $record = array();
- $records = $this->_getSelectableRecords($record, 'search');
- $result = '';
- foreach ($records as $rec)
- {
- $option = $this->m_destInstance->descriptor($rec);
- $value = $this->m_destInstance->primaryKey($rec);
- $result .= '
- <li title="'.atk_htmlentities($option).'">'.atk_htmlentities($option).'</li>';
- }
- return "<ul>$result</ul>";
- }
- /**
- * Creates a search filter with the given search value on the given
- * descriptor fields
- *
- * @param String $searchvalue A searchstring
- * @return String a search string (WHERE clause)
- */
- function createSearchFilter($searchvalue)
- {
- if($this->m_autocomplete_searchfields=="")
- $searchfields = $this->m_destInstance->descriptorFields();
- else
- $searchfields = $this->m_autocomplete_searchfields;
- $parts = preg_split('/\s+/', $searchvalue);
- $mainFilter = array();
- foreach ($parts as $part)
- {
- $filter = array();
- foreach($searchfields as $attribname)
- {
- if (strstr($attribname, '.')) $table = '';
- else $table = $this->m_destInstance->m_table.".";
- if(!$this->m_autocomplete_search_case_sensitive)
- $tmp = "LOWER(".$table.$attribname.")";
- else
- $tmp = $table.$attribname;
- switch($this->m_autocomplete_searchmode)
- {
- case self::SEARCH_MODE_EXACT:
- if(!$this->m_autocomplete_search_case_sensitive)
- $tmp.= " = LOWER('{$part}')";
- else
- $tmp.= " = '{$part}'";
- break;
- case self::SEARCH_MODE_STARTSWITH:
- if(!$this->m_autocomplete_search_case_sensitive)
- $tmp.= " LIKE LOWER('{$part}%')";
- else
- $tmp.= " LIKE '{$part}%'";
- break;
- case self::SEARCH_MODE_CONTAINS:
- if(!$this->m_autocomplete_search_case_sensitive)
- $tmp.= " LIKE LOWER('%{$part}%')";
- else
- $tmp.= " LIKE '%{$part}%'";
- break;
- default:
- $tmp.= " = LOWER('{$part}')";
- }
- $filter[] = $tmp;
- }
- if (count($filter) > 0)
- $mainFilter[] = "(".implode(") OR (", $filter).")";
- }
- if (count($mainFilter) > 0)
- $searchFilter = "(".implode(") AND (", $mainFilter).")";
- else $searchFilter = "";
- // When no searchfields are specified and we use the CONTAINS mode
- // add a concat filter
- if($this->m_autocomplete_searchmode == self::SEARCH_MODE_CONTAINS && $this->m_autocomplete_searchfields=="")
- {
- $filter = $this->getConcatFilter($searchvalue);
- if($filter)
- {
- if($searchFilter!='') $searchFilter.= " OR ";
- $searchFilter.= $filter;
- }
- }
- return $searchFilter;
- }
- /**
- * Get Concat filter
- *
- * @param string $searchValue Search value
- * @param string $fieldaliasprefix Field alias prefix
- * @return string|boolean
- */
- function getConcatFilter($searchValue,$fieldaliasprefix="")
- {
- // If we have a descriptor with multiple fields, use CONCAT
- $attribs = $this->m_destInstance->descriptorFields();
- if(count($attribs)>1)
- {
- $fields = array();
- foreach($attribs as $attribname)
- {
- $post = '';
- if (strstr($attribname, '.'))
- {
- if ($fieldaliasprefix != '') $table = $fieldaliasprefix.'_AE_';
- else $table = '';
- $post = substr($attribname,strpos($attribname,'.'));
- $attribname = substr($attribname,0,strpos($attribname,'.'));
- }
- elseif($fieldaliasprefix!='') $table = $fieldaliasprefix.".";
- else $table = $this->m_destInstance->m_table.".";
- $p_attrib = $this->m_destInstance->m_attribList[$attribname];
- $fields[$p_attrib->fieldName()] = $table.$p_attrib->fieldName().$post;
- }
- $value = $this->escapeSQL(trim($searchValue));
- $value = str_replace(" " , " ", $value);
- if(!$value)
- {
- return false;
- }
- else
- {
- $function = $this->getConcatDescriptorFunction();
- if ($function != '' && method_exists($this->m_destInstance, $function))
- {
- $descriptordef = $this->m_destInstance->$function();
- }
- elseif ($this->m_destInstance->m_descTemplate != NULL)
- {
- $descriptordef = $this->m_destInstance->m_descTemplate;
- }
- elseif(method_exists($this->m_destInstance,"descriptor_def"))
- {
- $descriptordef = $this->m_destInstance->descriptor_def();
- }
- else
- {
- $descriptordef = $this->m_destInstance->descriptor();
- }
- atkimport("atk.utils.atkstringparser");
- $parser = new atkStringParser($descriptordef);
- $concatFields = $parser->getAllParsedFieldsAsArray($fields, true);
- $concatTags = $concatFields['tags'];
- $concatSeparators = $concatFields['separators'];
- // to search independent of characters between tags, like spaces and comma's,
- // we remove all these separators so we can search for just the concatenated tags in concat_ws [Jeroen]
- foreach ($concatSeparators as $separator)
- {
- $value = str_replace($separator, "", $value);
- }
- $db = $this->getDb();
- $searchcondition = "UPPER(".$db->func_concat_ws($concatTags, "", true).") LIKE UPPER('%".$value."%')";
- }
- return $searchcondition;
- }
- return false;
- }
- }
- ?>