PageRenderTime 56ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/relations/class.atkmanytoonerelation.inc

https://github.com/ibuildingsnl/ATK
PHP | 2483 lines | 1458 code | 242 blank | 783 comment | 257 complexity | c0bf5b202591ec9c534a0eba89cd1723 MD5 | raw file
Possible License(s): LGPL-2.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, LGPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * This file is part of the Achievo ATK distribution.
  4. * Detailed copyright and licensing information can be found
  5. * in the doc/COPYRIGHT and doc/LICENSE files which should be
  6. * included in the distribution.
  7. *
  8. * @package atk
  9. * @subpackage relations
  10. *
  11. * @copyright (c)2000-2004 Ibuildings.nl BV
  12. * @copyright (c)2000-2004 Ivo Jansch
  13. * @license http://www.achievo.org/atk/licensing ATK Open Source License
  14. *
  15. * @version $Revision: 6309 $
  16. * $Id$
  17. */
  18. /**
  19. * Create edit/view links for the items in a manytoonerelation dropdown.
  20. */
  21. define("AF_RELATION_AUTOLINK", AF_SPECIFIC_1);
  22. /**
  23. * Create edit/view links for the items in a manytoonerelation dropdown.
  24. */
  25. define("AF_MANYTOONE_AUTOLINK", AF_RELATION_AUTOLINK);
  26. /**
  27. * Do not add null option under any circumstance
  28. */
  29. define("AF_RELATION_NO_NULL_ITEM", AF_SPECIFIC_2);
  30. /**
  31. * Do not add null option ever
  32. */
  33. define("AF_MANYTOONE_NO_NULL_ITEM", AF_RELATION_NO_NULL_ITEM);
  34. /**
  35. * Use auto-completition instead of drop-down / selection page
  36. */
  37. define("AF_RELATION_AUTOCOMPLETE", AF_SPECIFIC_3);
  38. /**
  39. * Use auto-completition instead of drop-down / selection page
  40. */
  41. define("AF_MANYTOONE_AUTOCOMPLETE", AF_RELATION_AUTOCOMPLETE);
  42. /**
  43. * Lazy load
  44. */
  45. define("AF_MANYTOONE_LAZY", AF_SPECIFIC_4);
  46. /**
  47. * Add a default null option to obligatory relations
  48. */
  49. define("AF_MANYTOONE_OBLIGATORY_NULL_ITEM", AF_SPECIFIC_5);
  50. /**
  51. * @internal include base class
  52. */
  53. userelation("atkrelation");
  54. /**
  55. * A N:1 relation between two classes.
  56. *
  57. * For example, projects all have one coordinator, but one
  58. * coordinator can have multiple projects. So in the project
  59. * class, there's a ManyToOneRelation to a coordinator.
  60. *
  61. * This relation essentially creates a dropdown box, from which
  62. * you can select from a set of records.
  63. *
  64. * @author Ivo Jansch <ivo@achievo.org>
  65. * @package atk
  66. * @subpackage relations
  67. *
  68. */
  69. class atkManyToOneRelation extends atkRelation
  70. {
  71. const SEARCH_MODE_EXACT = "exact";
  72. const SEARCH_MODE_STARTSWITH = "startswith";
  73. const SEARCH_MODE_CONTAINS = "contains";
  74. /**
  75. * By default, we do a left join. this means that records that don't have
  76. * a record in this relation, will be displayed anyway. NOTE: set this to
  77. * false only if you know what you're doing. When in doubt, 'true' is
  78. * usually the best option.
  79. * @var boolean
  80. */
  81. var $m_leftjoin = true;
  82. /**
  83. * The array of referential key fields.
  84. * @access private
  85. * @var array
  86. */
  87. var $m_refKey = array();
  88. /**
  89. * SQL statement with extra filter for the join that retrieves the
  90. * selected record.
  91. * @var String
  92. */
  93. var $m_joinFilter = "";
  94. /**
  95. * Hide the relation when there are no records to select.
  96. * @access private
  97. * @var boolean
  98. */
  99. var $m_hidewhenempty = false;
  100. /**
  101. * List columns.
  102. * @access private
  103. * @var Array
  104. */
  105. var $m_listColumns = array();
  106. /**
  107. * Always show list columns?
  108. * @access private
  109. * @var boolean
  110. */
  111. var $m_alwaysShowListColumns = false;
  112. /**
  113. * Label to use for the 'none' option.
  114. *
  115. * @access private
  116. * @var String
  117. */
  118. var $m_noneLabel = NULL;
  119. /**
  120. * Minimum number of character a user needs to enter before auto-completion kicks in.
  121. *
  122. * @access private
  123. * @var int
  124. */
  125. var $m_autocomplete_minchars = 2;
  126. /**
  127. * An array with the fieldnames of the destination node in which the autocompletion must search
  128. * for results.
  129. *
  130. * @access private
  131. * @var array
  132. */
  133. var $m_autocomplete_searchfields = "";
  134. /**
  135. * The search mode of the autocomplete fields. Can be 'startswith', 'exact' or 'contains'.
  136. *
  137. * @access private
  138. * @var String
  139. */
  140. var $m_autocomplete_searchmode = "contains";
  141. /**
  142. * Value determines wether the search of the autocompletion is case-sensitive.
  143. *
  144. * @var boolean
  145. */
  146. var $m_autocomplete_search_case_sensitive = false;
  147. /**
  148. * Value determines if select link for autocomplete should use atkSubmit or not (for use in admin screen for example)
  149. *
  150. * @var boolean
  151. */
  152. var $m_autocomplete_saveform = true;
  153. /**
  154. * Set the minimal number of records for showing the automcomplete. If there are less records
  155. * the normal dropdown is shown
  156. *
  157. * @access private
  158. * @var integer
  159. */
  160. var $m_autocomplete_minrecords = -1;
  161. /**
  162. * Destination node for auto links (edit, new)
  163. *
  164. * @var string
  165. */
  166. protected $m_autolink_destination = "";
  167. // override onchangehandler init
  168. var $m_onchangehandler_init = "newvalue = el.options[el.selectedIndex].value;\n";
  169. /**
  170. * Use destination filter for autolink add link?
  171. *
  172. * @access private
  173. * @var boolean
  174. */
  175. var $m_useFilterForAddLink = true;
  176. /**
  177. * Set a function to use for determining the descriptor in the getConcatFilter function
  178. *
  179. * @access private
  180. * @var string
  181. */
  182. var $m_concatDescriptorFunction = '';
  183. /**
  184. * When autosearch is set to true, this attribute will automatically submit
  185. * the search form onchange. This will only happen in the admin action.
  186. *
  187. * @var boolean
  188. */
  189. protected $m_autoSearch = false;
  190. /**
  191. * Selectable records for edit mode.
  192. *
  193. * @see atkManyToOneRelation::preAddToEditArray
  194. *
  195. * @var array
  196. */
  197. protected $m_selectableRecords = null;
  198. /**
  199. * Constructor.
  200. * @param String $name The name of the attribute. This is the name of the
  201. * field that is the referential key to the
  202. * destination.
  203. * For relations with more than one field in the
  204. * foreign key, you should pass an array of
  205. * referential key fields. The order of the fields
  206. * must match the order of the primary key attributes
  207. * in the destination node.
  208. * @param String $destination The node we have a relationship with.
  209. * @param int $flags Flags for the relation
  210. */
  211. function atkManyToOneRelation($name, $destination, $flags=0)
  212. {
  213. if (atkconfig("manytoone_autocomplete_default", false))
  214. $flags |= AF_RELATION_AUTOCOMPLETE;
  215. if (atkconfig("manytoone_autocomplete_large", true) && hasFlag($flags, AF_LARGE))
  216. $flags |= AF_RELATION_AUTOCOMPLETE;
  217. $this->m_autocomplete_minchars = atkconfig("manytoone_autocomplete_minchars", 2);
  218. $this->m_autocomplete_searchmode = atkconfig("manytoone_autocomplete_searchmode", "contains");
  219. $this->m_autocomplete_search_case_sensitive = atkconfig("manytoone_autocomplete_search_case_sensitive", false);
  220. if (is_array($name))
  221. {
  222. $this->m_refKey = $name;
  223. // ATK can't handle an array as name, so we initialize the
  224. // underlying attribute with the first name of the referential
  225. // keys.
  226. // Languagefiles, overrides, etc should use this first name to
  227. // override the relation.
  228. $this->atkRelation($name[0], $destination, $flags);
  229. }
  230. else
  231. {
  232. $this->m_refKey[] = $name;
  233. $this->atkRelation($name, $destination, $flags);
  234. }
  235. if ($this->hasFlag(AF_MANYTOONE_LAZY) && (count($this->m_refKey) > 1 || $this->m_refKey[0] != $this->fieldName()))
  236. {
  237. atkerror("AF_MANYTOONE_LAZY flag is not supported for multi-column reference key or a reference key that uses another column.");
  238. }
  239. }
  240. /**
  241. * Adds a flag to the manyToOne relation
  242. * Note that adding flags at any time after the constructor might not
  243. * always work. There are flags that are processed only at
  244. * constructor time.
  245. *
  246. * @param int $flag The flag to add to the attribute
  247. * @return atkManyToOneRelation The instance of this atkManyToOneRelation
  248. */
  249. function addFlag($flag)
  250. {
  251. parent::addFlag($flag);
  252. if (atkconfig("manytoone_autocomplete_large", true) && hasFlag($flag, AF_LARGE))
  253. $this->m_flags |= AF_RELATION_AUTOCOMPLETE;
  254. return $this;
  255. }
  256. /**
  257. * When autosearch is set to true, this attribute will automatically submit
  258. * the search form onchange. This will only happen in the admin action.
  259. * @param bool $auto
  260. * @return void
  261. */
  262. public function setAutoSearch($auto = false)
  263. {
  264. $this->m_autoSearch = $auto;
  265. }
  266. /**
  267. * Set join filter.
  268. *
  269. * @param string $filter join filter
  270. */
  271. function setJoinFilter($filter)
  272. {
  273. $this->m_joinFilter = $filter;
  274. }
  275. /**
  276. * Set the searchfields for the autocompletion.
  277. *
  278. * @param array $searchfields
  279. */
  280. function setAutoCompleteSearchFields($searchfields)
  281. {
  282. $this->m_autocomplete_searchfields = $searchfields;
  283. }
  284. /**
  285. * Set the searchmode for the autocompletion:
  286. * exact, startswith(default) or contains.
  287. *
  288. * @param array $mode
  289. */
  290. function setAutoCompleteSearchMode($mode)
  291. {
  292. $this->m_autocomplete_searchmode = $mode;
  293. }
  294. /**
  295. * Set the case-sensitivity for the autocompletion search (true or false).
  296. *
  297. * @param array $case_sensitive
  298. */
  299. function setAutoCompleteCaseSensitive($case_sensitive)
  300. {
  301. $this->m_autocomplete_search_case_sensitive = $case_sensitive;
  302. }
  303. /**
  304. * Sets the minimum number of characters before auto-completion kicks in.
  305. *
  306. * @param int $chars
  307. */
  308. function setAutoCompleteMinChars($chars)
  309. {
  310. $this->m_autocomplete_minchars = $chars;
  311. }
  312. /**
  313. * Set if the select link should save form (atkSubmit) or not (for use in admin screen for example)
  314. *
  315. * @param boolean $saveform
  316. */
  317. function setAutoCompleteSaveForm($saveform=true)
  318. {
  319. $this->m_autocomplete_saveform = $saveform;
  320. }
  321. /**
  322. * Set the minimal number of records for the autocomplete to show
  323. * If there are less records the normal dropdown is shown
  324. *
  325. * @param integer $minrecords
  326. */
  327. function setAutoCompleteMinRecords($minrecords)
  328. {
  329. $this->m_autocomplete_minrecords = $minrecords;
  330. }
  331. /**
  332. * Use destination filter for auto add link?
  333. *
  334. * @param boolean $useFilter use destnation filter for add link?
  335. */
  336. function setUseFilterForAddLink($useFilter)
  337. {
  338. $this->m_useFilterForAddLink = $useFilter;
  339. }
  340. /**
  341. * Set the function for determining the descriptor in the getConcatFilter function
  342. * This function should be implemented in the destination node
  343. *
  344. * @param string $function
  345. */
  346. function setConcatDescriptorFunction($function)
  347. {
  348. $this->m_concatDescriptorFunction = $function;
  349. }
  350. /**
  351. * Return the function for determining the descriptor in the getConcatFilter function
  352. *
  353. * @return string
  354. */
  355. function getConcatDescriptorFunction()
  356. {
  357. return $this->m_concatDescriptorFunction;
  358. }
  359. /**
  360. * Add list column. An attribute of the destination node
  361. * that (only) will be displayed in the recordlist.
  362. *
  363. * @param string $attr The attribute to add to the listcolumn
  364. * @return atkManyToOneRelation The instance of this atkManyToOneRelation
  365. */
  366. function addListColumn($attr)
  367. {
  368. $this->m_listColumns[] = $attr;
  369. return $this;
  370. }
  371. /**
  372. * Add multiple list columns. Attributes of the destination node
  373. * that (only) will be displayed in the recordlist.
  374. * @return atkManyToOneRelation The instance of this atkManyToOneRelation
  375. */
  376. function addListColumns()
  377. {
  378. $attrs = func_get_args();
  379. foreach ($attrs as $attr)
  380. $this->m_listColumns[] = $attr;
  381. return $this;
  382. }
  383. public function getListColumns()
  384. {
  385. return $this->m_listColumns;
  386. }
  387. /**
  388. * Reset the list columns and add multiple list columns. Attributes of the
  389. * destination node that (only) will be displayed in the recordlist.
  390. * @return atkManyToOneRelation The instance of this atkManyToOneRelation
  391. */
  392. public function setListColumns()
  393. {
  394. $this->m_listColumns = array();
  395. $attrs = func_get_args();
  396. if (count($attrs)===1 && is_array($attrs[0]))
  397. {
  398. $columns = $attrs[0];
  399. }
  400. else
  401. {
  402. $columns = $attrs;
  403. }
  404. foreach ($columns as $column)
  405. {
  406. $this->m_listColumns[] = $column;
  407. }
  408. return $this;
  409. }
  410. /**
  411. * Always show list columns in list view,
  412. * even if the attribute itself is hidden?
  413. *
  414. * @param bool $value always show list columns?
  415. * @return atkManyToOneRelation The instance of this atkManyToOneRelation
  416. */
  417. function setAlwaysShowListColumns($value)
  418. {
  419. $this->m_alwaysShowListColumns = $value;
  420. if($this->m_alwaysShowListColumns)
  421. $this->addFlag(AF_FORCE_LOAD);
  422. return $this;
  423. }
  424. /**
  425. * Convert value to DataBase value
  426. * @param array $rec Record to convert
  427. * @return int Database safe value
  428. */
  429. function value2db($rec)
  430. {
  431. if ($this->isEmpty($rec))
  432. {
  433. atkdebug($this->fieldName()." IS EMPTY!");
  434. return NULL;
  435. }
  436. else
  437. {
  438. if ($this->createDestination())
  439. {
  440. if (is_array($rec[$this->fieldName()]))
  441. {
  442. $pkfield = $this->m_destInstance->m_primaryKey[0];
  443. $pkattr = &$this->m_destInstance->getAttribute($pkfield);
  444. return $pkattr->value2db($rec[$this->fieldName()]);
  445. }
  446. else
  447. {
  448. return $rec[$this->fieldName()];
  449. }
  450. }
  451. }
  452. // This never happens, does it?
  453. return "";
  454. }
  455. /**
  456. * Fetch value out of record
  457. * @param array $postvars Postvars
  458. * @return decoded value
  459. */
  460. function fetchValue($postvars)
  461. {
  462. if ($this->isPosted($postvars))
  463. {
  464. $result = array();
  465. // support specifying the value as a single number if the
  466. // destination's primary key consists of a single field
  467. if (is_numeric($postvars[$this->fieldName()]))
  468. {
  469. $result[$this->getDestination()->primaryKeyField()] = $postvars[$this->fieldName()];
  470. }
  471. else
  472. {
  473. // Split the primary key of the selected record into its
  474. // referential key elements.
  475. $keyelements = decodeKeyValueSet($postvars[$this->fieldName()]);
  476. foreach ($keyelements as $key=>$value)
  477. {
  478. // Tablename must be stripped out because it is in the way..
  479. if (strpos($key,'.')>0)
  480. {
  481. $field = substr($key,strrpos($key,'.')+1);
  482. }
  483. else
  484. {
  485. $field = $key;
  486. }
  487. $result[$field] = $value;
  488. }
  489. }
  490. if (count($result) == 0)
  491. {
  492. return null;
  493. }
  494. // add descriptor fields, this means they can be shown in the title
  495. // bar etc. when updating failed for example
  496. $record = array($this->fieldName() => $result);
  497. $this->populate($record);
  498. $result = $record[$this->fieldName()];
  499. return $result;
  500. }
  501. return NULL;
  502. }
  503. /**
  504. * Converts DataBase value to normal value
  505. * @param array $rec Record
  506. * @return decoded value
  507. */
  508. function db2value($rec)
  509. {
  510. $this->createDestination();
  511. if (isset($rec[$this->fieldName()]) &&
  512. is_array($rec[$this->fieldName()]) &&
  513. (!isset($rec[$this->fieldName()][$this->m_destInstance->primaryKeyField()]) ||
  514. empty($rec[$this->fieldName()][$this->m_destInstance->primaryKeyField()])))
  515. {
  516. return NULL;
  517. }
  518. if (isset($rec[$this->fieldName()]))
  519. {
  520. $myrec = $rec[$this->fieldName()];
  521. if (is_array($myrec))
  522. {
  523. $result = array();
  524. if ($this->createDestination())
  525. {
  526. foreach (array_keys($this->m_destInstance->m_attribList) as $attrName)
  527. {
  528. $attr = &$this->m_destInstance->m_attribList[$attrName];
  529. $result[$attrName] = $attr->db2value($myrec);
  530. }
  531. }
  532. return $result;
  533. }
  534. else
  535. {
  536. // if the record is not an array, probably only the value of the primary key was loaded.
  537. // This workaround only works for single-field primary keys.
  538. if ($this->createDestination())
  539. return array($this->m_destInstance->primaryKeyField() => $myrec);
  540. }
  541. }
  542. }
  543. /**
  544. * Set none label.
  545. *
  546. * @param string $label The label to use for the "none" option
  547. */
  548. function setNoneLabel($label)
  549. {
  550. $this->m_noneLabel = $label;
  551. }
  552. /**
  553. * Get none label.
  554. *
  555. * @return String The label for the "none" option
  556. */
  557. function getNoneLabel()
  558. {
  559. if ($this->m_noneLabel !== NULL)
  560. return $this->m_noneLabel;
  561. $nodename = $this->m_destInstance->m_type;
  562. $modulename = $this->m_destInstance->m_module;
  563. $ownermodulename = $this->m_ownerInstance->m_module;
  564. $label = atktext($this->fieldName() . '_select_none', $ownermodulename, $this->m_owner, "", "", true);
  565. if ($label == "")
  566. $label = atktext('select_none', $modulename, $nodename);
  567. return $label;
  568. }
  569. /**
  570. * Returns a displayable string for this value.
  571. *
  572. * @param array $record The record that holds the value for this attribute
  573. * @param String $mode The display mode ("view" for viewpages, or "list"
  574. * for displaying in recordlists, "edit" for
  575. * displaying in editscreens, "add" for displaying in
  576. * add screens. "csv" for csv files. Applications can
  577. * use additional modes.
  578. * @return a displayable string
  579. */
  580. function display($record, $mode='list')
  581. {
  582. if ($this->createDestination())
  583. {
  584. if (count($record[$this->fieldName()])==count($this->m_refKey))
  585. $this->populate($record);
  586. if(!$this->isEmpty($record))
  587. {
  588. $result = $this->m_destInstance->descriptor($record[$this->fieldName()]);
  589. if ($this->hasFlag(AF_RELATION_AUTOLINK) && (!in_array($mode, array("csv", "plain", "list")))) // create link to edit/view screen
  590. {
  591. if (($this->m_destInstance->allowed("view"))&&!$this->m_destInstance->hasFlag(NF_NO_VIEW)&&$result!="")
  592. {
  593. $saveForm = $mode == 'add' || $mode == 'edit';
  594. $result = href(dispatch_url($this->m_destination,"view",array("atkselector"=>$this->m_destInstance->primaryKey($record[$this->fieldName()]))), $result, SESSION_NESTED, $saveForm);
  595. }
  596. }
  597. }
  598. else
  599. {
  600. $result = (!in_array($mode, array("csv", "plain"))?$this->getNoneLabel():''); // no record
  601. }
  602. return $result;
  603. }
  604. else
  605. {
  606. atkdebug("Can't create destination! ($this->m_destination");
  607. }
  608. return "";
  609. }
  610. /**
  611. * Populate the record with the destination record data.
  612. *
  613. * @param array $record record
  614. * @param mixed $fullOrFields load all data, only the given fields or only the descriptor fields?
  615. */
  616. public function populate(&$record, $fullOrFields=false)
  617. {
  618. if (!is_array($record)||$record[$this->fieldName()] == "") return;
  619. atkdebug("Delayed loading of ".($fullOrFields || is_array($fullOrFields) ? "" : "descriptor ")."fields for ".$this->m_name);
  620. $this->createDestination();
  621. $includes = "";
  622. if (is_array($fullOrFields))
  623. {
  624. $includes = array_merge($this->m_destInstance->m_primaryKey, $fullOrFields);
  625. }
  626. else if (!$fullOrFields)
  627. {
  628. $includes = $this->m_destInstance->descriptorFields();
  629. }
  630. $result =
  631. $this->m_destInstance
  632. ->select($this->m_destInstance->primaryKey($record[$this->fieldName()]))
  633. ->orderBy($this->m_destInstance->getColumnConfig()->getOrderByStatement())
  634. ->includes($includes)
  635. ->firstRow();
  636. if ($result != null)
  637. {
  638. $record[$this->fieldName()] = $result;
  639. }
  640. }
  641. /**
  642. * Creates HTML for the selection and auto links.
  643. *
  644. * @param string $id attribute id
  645. * @param array $record record
  646. * @return string
  647. */
  648. function createSelectAndAutoLinks($id, $record)
  649. {
  650. $links = array();
  651. $newsel = $id;
  652. $filter = $this->parseFilter($this->m_destinationFilter, $record);
  653. $links[] = $this->_getSelectLink($newsel, $filter);
  654. if ($this->hasFlag(AF_RELATION_AUTOLINK)) // auto edit/view link
  655. {
  656. if ($this->m_destInstance->allowed("add"))
  657. {
  658. $links[] = href(dispatch_url($this->getAutoLinkDestination(),"add",array("atkpkret"=>$id,"atkfilter"=>($filter!=""?$filter:""))),
  659. atktext("new"),SESSION_NESTED,true);
  660. }
  661. if ($this->m_destInstance->allowed("edit") && $record[$this->fieldName()] != NULL)
  662. {
  663. //we laten nu altijd de edit link zien, maar eigenlijk mag dat niet, want
  664. //de app crasht als er geen waarde is ingevuld.
  665. $editUrl = session_url(dispatch_url($this->getAutoLinkDestination(),"edit",array("atkselector"=>"REPLACEME")),SESSION_NESTED);
  666. $links[] = "<span id=\"".$id."_edit\" style=\"\"><a href='javascript:atkSubmit(mto_parse(\"".atkurlencode($editUrl)."\", document.entryform.".$id.".value))'>".atktext('edit')."</a></span>";
  667. }
  668. }
  669. return implode("&nbsp;", $links);
  670. }
  671. /**
  672. * Set destination node for the Autolink links (new/edit)
  673. *
  674. * @param string $node
  675. */
  676. function setAutoLinkDestination($node)
  677. {
  678. $this->m_autolink_destination = $node;
  679. }
  680. /**
  681. * Get destination node for the Autolink links (new/edit)
  682. *
  683. * @return string
  684. */
  685. function getAutoLinkDestination()
  686. {
  687. if(!empty($this->m_autolink_destination))
  688. {
  689. return $this->m_autolink_destination;
  690. }
  691. return $this->m_destination;
  692. }
  693. /**
  694. * Prepare for editing, make sure we already have the selectable records
  695. * loaded and update the record with the possible selection of the first
  696. * record.
  697. *
  698. * @param array $record reference to the record
  699. * @param string $fieldPrefix field prefix
  700. * @param string $mode edit mode
  701. */
  702. public function preAddToEditArray(&$record, $fieldPrefix, $mode)
  703. {
  704. if ((!$this->hasFlag(AF_RELATION_AUTOCOMPLETE) && !$this->hasFlag(AF_LARGE)) ||
  705. $this->m_autocomplete_minrecords > -1)
  706. {
  707. $this->m_selectableRecords = $this->_getSelectableRecords($record, 'select');
  708. if (count($this->m_selectableRecords) > 0 &&
  709. !atkconfig("list_obligatory_null_item") &&
  710. (($this->hasFlag(AF_OBLIGATORY) && !$this->hasFlag(AF_MANYTOONE_OBLIGATORY_NULL_ITEM)) ||
  711. (!$this->hasFlag(AF_OBLIGATORY) && $this->hasFlag(AF_RELATION_NO_NULL_ITEM))))
  712. {
  713. if (!isset($record[$this->fieldName()]) || !is_array($record[$this->fieldName()]))
  714. {
  715. $record[$this->fieldName()] = $this->m_selectableRecords[0];
  716. }
  717. else if (!$this->_isSelectableRecord($record, 'select'))
  718. {
  719. $record[$this->fieldName()] = $this->m_selectableRecords[0];
  720. }
  721. else
  722. {
  723. $current = $this->getDestination()->primaryKey($record[$this->fieldName()]);
  724. $record[$this->fieldName()] = null;
  725. foreach ($this->m_selectableRecords as $selectable)
  726. {
  727. if ($this->getDestination()->primaryKey($selectable) == $current)
  728. {
  729. $record[$this->fieldName()] = $selectable;
  730. break;
  731. }
  732. }
  733. }
  734. }
  735. }
  736. else if (is_array($record[$this->fieldName()]) && !$this->_isSelectableRecord($record, 'select'))
  737. {
  738. $record[$this->fieldName()] = null;
  739. }
  740. else if (is_array($record[$this->fieldName()]))
  741. {
  742. $this->populate($record);
  743. }
  744. }
  745. /**
  746. * Returns a piece of html code that can be used in a form to edit this
  747. * attribute's value.
  748. * @param array $record The record that holds the value for this attribute.
  749. * @param String $fieldprefix The fieldprefix to put in front of the name
  750. * of any html form element for this attribute.
  751. * @param String $mode The mode we're in ('add' or 'edit')
  752. * @return Piece of html code that can be used in a form to edit this
  753. */
  754. function edit($record, $fieldprefix="", $mode="edit")
  755. {
  756. if (!$this->createDestination())
  757. {
  758. return atkerror("Could not create destination for destination: $this->m_destination!");
  759. }
  760. $recordset = $this->m_selectableRecords;
  761. // load records for bwc
  762. if ($recordset === null && $this->hasFlag(AF_RELATION_AUTOCOMPLETE) && $this->m_autocomplete_minrecords > -1)
  763. {
  764. $recordset = $this->_getSelectableRecords($record, 'select');
  765. }
  766. 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))
  767. {
  768. return $this->drawAutoCompleteBox($record, $fieldprefix, $mode);
  769. }
  770. $id = $fieldprefix.$this->fieldName();
  771. $filter = $this->parseFilter($this->m_destinationFilter, $record);
  772. $autolink = $this->getRelationAutolink($id, $filter);
  773. $editflag = true;
  774. $value = isset($record[$this->fieldName()]) ? $record[$this->fieldName()] : null;
  775. $currentPk = $value != null ? $this->getDestination()->primaryKey($value) : null;
  776. if (!$this->hasFlag(AF_LARGE)) // normal dropdown..
  777. {
  778. // load records for bwc
  779. if ($recordset == null)
  780. {
  781. $recordset = $this->_getSelectableRecords($record, 'select');
  782. }
  783. if (count($recordset) == 0)
  784. {
  785. $editflag = false;
  786. }
  787. $onchange='';
  788. if (count($this->m_onchangecode))
  789. {
  790. $onchange = 'onChange="'.$id.'_onChange(this);"';
  791. $this->_renderChangeHandler($fieldprefix);
  792. }
  793. // autoselect if there is only one record (if obligatory is not set,
  794. // we don't autoselect, since user may wist to select 'none' instead
  795. // of the 1 record.
  796. $result = '';
  797. if (count($recordset) == 0)
  798. {
  799. $result = $this->getNoneLabel();
  800. }
  801. else
  802. {
  803. $this->registerKeyListener($id, KB_CTRLCURSOR|KB_LEFTRIGHT);
  804. $this->registerJavaScriptObservers($id);
  805. $result = '<select id="'.$id.'" name="'.$id.'" class="atkmanytoonerelation" '.$onchange.'>';
  806. // relation may be empty, so we must provide an empty selectable..
  807. if ($this->hasFlag(AF_MANYTOONE_OBLIGATORY_NULL_ITEM) ||
  808. (!$this->hasFlag(AF_OBLIGATORY) && !$this->hasFlag(AF_RELATION_NO_NULL_ITEM)) ||
  809. (atkconfig("list_obligatory_null_item") && !is_array($value)))
  810. {
  811. $result.= '<option value="">'.$this->getNoneLabel();
  812. }
  813. foreach ($recordset as $selectable)
  814. {
  815. $pk = $this->getDestination()->primaryKey($selectable);
  816. $sel = $pk == $currentPk ? 'selected="selected"' : '';
  817. $result .= '<option value="'.$pk.'" '.$sel.'>'.str_replace(' ', '&nbsp;', atk_htmlentities($this->m_destInstance->descriptor($selectable)));
  818. }
  819. $result .= '</select>';
  820. }
  821. }
  822. else
  823. {
  824. $destrecord = $record[$this->fieldName()];
  825. if (is_array($destrecord))
  826. {
  827. $result.= '<span id="'.$id.'_current" >'.$this->m_destInstance->descriptor($destrecord)."&nbsp;&nbsp;";
  828. if (!$this->hasFlag(AF_OBLIGATORY))
  829. {
  830. $result.= '<a href="#" onClick="document.getElementById(\''.
  831. $id.'\').value=\'\'; document.getElementById(\''.$id.'_current\').style.display=\'none\'">'.atktext("unselect").'</a>&nbsp;&nbsp;';
  832. }
  833. $result.= '</span>';
  834. }
  835. $result .= $this->hide($record, $fieldprefix);
  836. $result .= $this->_getSelectLink($id, $filter);
  837. }
  838. $result .= $editflag && isset($autolink['edit']) ? $autolink['edit'] : "";
  839. $result .= isset($autolink['add']) ? $autolink['add'] : "";
  840. return $result;
  841. }
  842. /**
  843. * Get the select link to select the value using a select action on the destination node
  844. *
  845. * @param string $selname
  846. * @param string $filter
  847. * @return String HTML-code with the select link
  848. */
  849. function _getSelectLink($selname, $filter)
  850. {
  851. $result = "";
  852. // we use the current level to automatically return to this page
  853. // when we come from the select..
  854. $atktarget = atkurlencode(getDispatchFile()."?atklevel=".atkLevel()."&".$selname."=[atkprimkey]");
  855. $linkname = atktext("link_select_".getNodeType($this->m_destination), $this->getOwnerInstance()->getModule(), $this->getOwnerInstance()->getType(),'','',true);
  856. if (!$linkname) $linkname = atktext("link_select_".getNodeType($this->m_destination), getNodeModule($this->m_destination),getNodeType($this->m_destination),'','',true);
  857. if (!$linkname) $linkname = atktext("select_a").' '.strtolower(atktext(getNodeType($this->m_destination), getNodeModule($this->m_destination),getNodeType($this->m_destination)));
  858. if ($this->m_destinationFilter!="")
  859. {
  860. $result.= href(dispatch_url($this->m_destination,"select",array("atkfilter"=>$filter,"atktarget"=>$atktarget)),
  861. $linkname,
  862. SESSION_NESTED,
  863. $this->m_autocomplete_saveform,'class="atkmanytoonerelation"');
  864. }
  865. else
  866. {
  867. $result.= href(dispatch_url($this->m_destination,"select",array("atktarget"=>$atktarget)),
  868. $linkname,
  869. SESSION_NESTED,
  870. $this->m_autocomplete_saveform,'class="atkmanytoonerelation"');
  871. }
  872. return $result;
  873. }
  874. /**
  875. * Creates and returns the auto edit/view links
  876. * @param String $id The field id
  877. * @param String $filter Filter that we want to apply on the destination node
  878. * @return array The HTML code for the autolink links
  879. */
  880. function getRelationAutolink($id, $filter)
  881. {
  882. $autolink = array();
  883. if ($this->hasFlag(AF_RELATION_AUTOLINK)) // auto edit/view link
  884. {
  885. $page = &atkPage::getInstance();
  886. $page->register_script(atkconfig("atkroot")."atk/javascript/class.atkmanytoonerelation.js");
  887. if ($this->m_destInstance->allowed("edit"))
  888. {
  889. $editlink = session_url(dispatch_url($this->getAutoLinkDestination(),"edit",array("atkselector"=>"REPLACEME")),SESSION_NESTED);
  890. $autolink['edit'] = "&nbsp;<a href='javascript:atkSubmit(mto_parse(\"".atkurlencode($editlink)."\", document.entryform.".$id.".value))'>".atktext('edit')."</a>";
  891. }
  892. if ($this->m_destInstance->allowed("add"))
  893. {
  894. $autolink['add'] = "&nbsp;".href(dispatch_url($this->getAutoLinkDestination(),"add",array("atkpkret"=>$id,"atkfilter"=>($this->m_useFilterForAddLink && $filter != "" ? $filter : ""))),
  895. atktext("new"),SESSION_NESTED,true);
  896. }
  897. }
  898. return $autolink;
  899. }
  900. /**
  901. * Returns a piece of html code for hiding this attribute in an HTML form,
  902. * while still posting its value. (<input type="hidden">)
  903. *
  904. * @param array $record The record that holds the value for this attribute
  905. * @param String $fieldprefix The fieldprefix to put in front of the name
  906. * of any html form element for this attribute.
  907. * @return String A piece of htmlcode with hidden form elements that post
  908. * this attribute's value without showing it.
  909. */
  910. function hide($record="", $fieldprefix="")
  911. {
  912. if (!$this->createDestination()) return '';
  913. $currentPk = "";
  914. if (isset($record[$this->fieldName()]) && $record[$this->fieldName()] != null)
  915. {
  916. $this->fixDestinationRecord($record);
  917. $currentPk = $this->m_destInstance->primaryKey($record[$this->fieldName()]);
  918. }
  919. $result =
  920. '<input type="hidden" id="'.$fieldprefix.$this->formName().'"
  921. name="'.$fieldprefix.$this->formName().'"
  922. value="'.$currentPk.'">';
  923. return $result;
  924. }
  925. /**
  926. * Support for destination "records" where only the id is set and the
  927. * record itself isn't converted to a real record (array) yet
  928. *
  929. * @param array $record The record to fix
  930. */
  931. function fixDestinationRecord(&$record)
  932. {
  933. if ($this->createDestination() &&
  934. isset($record[$this->fieldName()]) &&
  935. $record[$this->fieldName()] != null &&
  936. !is_array($record[$this->fieldName()]))
  937. {
  938. $record[$this->fieldName()] = array($this->m_destInstance->primaryKeyField() => $record[$this->fieldName()]);
  939. }
  940. }
  941. /**
  942. * Retrieve the html code for placing this attribute in an edit page.
  943. *
  944. * The difference with the edit() method is that the edit() method just
  945. * generates the HTML code for editing the attribute, while the getEdit()
  946. * method is 'smart', and implements a hide/readonly policy based on
  947. * flags and/or custom override methodes in the node.
  948. * (<attributename>_edit() and <attributename>_display() methods)
  949. *
  950. * Framework method, it should not be necessary to call this method
  951. * directly.
  952. *
  953. * @param String $mode The edit mode ("add" or "edit")
  954. * @param array $record The record holding the values for this attribute
  955. * @param String $fieldprefix The fieldprefix to put in front of the name
  956. * of any html form element for this attribute.
  957. * @return String the HTML code for this attribute that can be used in an
  958. * editpage.
  959. */
  960. function getEdit($mode, &$record, $fieldprefix)
  961. {
  962. $this->fixDestinationRecord($record);
  963. return parent::getEdit($mode, $record, $fieldprefix);
  964. }
  965. /**
  966. * Converts a record filter to a record array.
  967. *
  968. * @param string $filter filter string
  969. * @return array record
  970. */
  971. protected function filterToArray($filter)
  972. {
  973. $result = array();
  974. $values = decodeKeyValueSet($filter);
  975. foreach ($values as $field => $value)
  976. {
  977. $parts = explode('.', $field);
  978. $ref = &$result;
  979. foreach ($parts as $part)
  980. {
  981. $ref = &$ref[$part];
  982. }
  983. $ref = $value;
  984. }
  985. return $result;
  986. }
  987. /**
  988. * Returns a piece of html code that can be used to get search terms input
  989. * from the user.
  990. *
  991. * @param array $record Array with values
  992. * @param boolean $extended if set to false, a simple search input is
  993. * returned for use in the searchbar of the
  994. * recordlist. If set to true, a more extended
  995. * search may be returned for the 'extended'
  996. * search page. The atkAttribute does not
  997. * make a difference for $extended is true, but
  998. * derived attributes may reimplement this.
  999. * @param string $fieldprefix The fieldprefix of this attribute's HTML element.
  1000. * @param atkDataGrid $grid The datagrid
  1001. *
  1002. * @return String A piece of html-code
  1003. */
  1004. function search($record=array(), $extended=false, $fieldprefix="", atkDataGrid $grid=null)
  1005. {
  1006. $useautocompletion = atkConfig("manytoone_search_autocomplete", true) && $this->hasFlag(AF_RELATION_AUTOCOMPLETE);
  1007. if (!$this->hasFlag(AF_LARGE) && !$useautocompletion)
  1008. {
  1009. if ($this->createDestination())
  1010. {
  1011. if ($this->m_destinationFilter!="")
  1012. {
  1013. $filterRecord = array();
  1014. if ($grid != null)
  1015. {
  1016. foreach ($grid->getFilters() as $filter)
  1017. {
  1018. $filter = $filter['filter'];
  1019. $arr = $this->filterToArray($filter);
  1020. $arr = (isset($arr[$this->getOwnerInstance()->m_table]) && is_array($arr[$this->getOwnerInstance()->m_table])) ? $arr[$this->getOwnerInstance()->m_table] : array();
  1021. foreach ($arr as $attrName => $value)
  1022. {
  1023. $attr = $this->getOwnerInstance()->getAttribute($attrName);
  1024. if (!is_array($value) && is_a($attr, 'atkManyToOneRelation') && count($attr->m_refKey) == 1)
  1025. {
  1026. $attr->createDestination();
  1027. $arr[$attrName] = array($attr->getDestination()->primaryKeyField() => $value);
  1028. }
  1029. }
  1030. $filterRecord = array_merge($filterRecord, $arr);
  1031. }
  1032. }
  1033. $record = array_merge($filterRecord, is_array($record) ? $record : array());
  1034. }
  1035. $recordset = $this->_getSelectableRecords($record, 'search');
  1036. $result = '<select class="'.get_class($this).'" ';
  1037. if ($extended)
  1038. {
  1039. $result.='multiple size="'.min(5,count($recordset)+1).'"';
  1040. if(isset($record[$this->fieldName()][$this->fieldName()]))
  1041. $record[$this->fieldName()] = $record[$this->fieldName()][$this->fieldName()];
  1042. }
  1043. // if we use autosearch, register an onchange event that submits the grid
  1044. if (!is_null($grid) && !$extended && $this->m_autoSearch) {
  1045. $id = $this->getSearchFieldName($fieldprefix);
  1046. $result .= ' id="'.$id.'" ';
  1047. $code = '$(\''.$id.'\').observe(\'change\', function(event) { ' .
  1048. $grid->getUpdateCall(array('atkstartat' => 0), array(), 'ATK.DataGrid.extractSearchOverrides') .
  1049. ' return false; });'
  1050. ;
  1051. $this->getOwnerInstance()->getPage()->register_loadscript($code);
  1052. }
  1053. $result.='name="'.$this->getSearchFieldName($fieldprefix).'[]">';
  1054. $pkfield = $this->m_destInstance->primaryKeyField();
  1055. $result.= '<option value="">'.atktext('search_all').'</option>';
  1056. if (!$this->hasFlag(AF_OBLIGATORY))
  1057. $result.= '<option value="__NONE__"'.(isset($record[$this->fieldName()]) && atk_in_array('__NONE__', $record[$this->fieldName()]) ? ' selected="selected"' : '').'>'.atktext('search_none').'</option>';
  1058. for ($i=0;$i<count($recordset);$i++)
  1059. {
  1060. $pk = $recordset[$i][$pkfield];
  1061. if (is_array($record)&&isset($record[$this->fieldName()])&&
  1062. atk_in_array($pk, $record[$this->fieldName()])) $sel = "selected"; else $sel = "";
  1063. $result.= '<option value="'.$pk.'" '.$sel.'>'.str_replace(' ', '&nbsp;', atk_htmlentities($this->m_destInstance->descriptor($recordset[$i]))).'</option>';
  1064. }
  1065. $result.='</select>';
  1066. return $result;
  1067. }
  1068. return "";
  1069. }
  1070. else
  1071. {
  1072. $id = $this->getSearchFieldName($fieldprefix);
  1073. if(is_array($record[$this->fieldName()]) && isset($record[$this->fieldName()][$this->fieldName()]))
  1074. $record[$this->fieldName()] = $record[$this->fieldName()][$this->fieldName()];
  1075. $this->registerKeyListener($id, KB_CTRLCURSOR|KB_UPDOWN);
  1076. $result = '<input type="text" id="'.$id.'" class="'.get_class($this).'" name="'.$id.'" value="'.$record[$this->fieldName()].'"'.
  1077. ($useautocompletion ? ' onchange=""' : '').
  1078. ($this->m_searchsize > 0 ? ' size="'.$this->m_searchsize.'"' : '').
  1079. ($this->m_maxsize > 0 ? ' maxlength="'.$this->m_maxsize.'"' : '').'>';
  1080. if ($useautocompletion)
  1081. {
  1082. $page = &$this->m_ownerInstance->getPage();
  1083. $url = partial_url($this->m_ownerInstance->atkNodeType(), $this->m_ownerInstance->m_action, 'attribute.'.$this->fieldName().'.autocomplete_search');
  1084. $code = "ATK.ManyToOneRelation.completeSearch('{$id}', '{$id}_result', '{$url}', {$this->m_autocomplete_minchars});";
  1085. $this->m_ownerInstance->addStyle("atkmanytoonerelation.css");
  1086. $page->register_script(atkconfig('atkroot').'atk/javascript/class.atkmanytoonerelation.js');
  1087. $page->register_loadscript($code);
  1088. $result .= '<div id="'.$id.'_result" style="display: none" class="atkmanytoonerelation_result"></div>';
  1089. }
  1090. return $result;
  1091. }
  1092. }
  1093. /**
  1094. * Retrieve the list of searchmodes supported by the attribute.
  1095. *
  1096. * Note that not all modes may be supported by the database driver.
  1097. * Compare this list to the one returned by the databasedriver, to
  1098. * determine which searchmodes may be used.
  1099. *
  1100. * @return array List of supported searchmodes
  1101. */
  1102. function getSearchModes()
  1103. {
  1104. if ($this->hasFlag(AF_LARGE) || $this->hasFlag(AF_MANYTOONE_AUTOCOMPLETE))
  1105. {
  1106. return array("substring","exact","wildcard","regex");
  1107. }
  1108. return array("exact"); // only support exact search when searching with dropdowns
  1109. }
  1110. /**
  1111. * Creates a smart search condition for a given search value, and adds it
  1112. * to the query that will be used for performing the actual search.
  1113. *
  1114. * @param Integer $id The unique smart search criterium identifier.
  1115. * @param Integer $nr The element number in the path.
  1116. * @param Array $path The remaining attribute path.
  1117. * @param atkQuery $query The query to which the condition will be added.
  1118. * @param String $ownerAlias The owner table alias to use.
  1119. * @param Mixed $value The value the user has entered in the searchbox.
  1120. * @param String $mode The searchmode to use.
  1121. */
  1122. function smartSearchCondition($id, $nr, $path, &$query, $ownerAlias, $value, $mode)
  1123. {
  1124. if (count($path) > 0)
  1125. {
  1126. $this->createDestination();
  1127. $destAlias = "ss_{$id}_{$nr}_".$this->fieldName();
  1128. $query->addJoin(
  1129. $this->m_destInstance->m_table, $destAlias,
  1130. $this->getJoinCondition($query, $ownerAlias, $destAlias),
  1131. false
  1132. );
  1133. $attrName = array_shift($path);
  1134. $attr = &$this->m_destInstance->getAttribute($attrName);
  1135. if (is_object($attr))
  1136. {
  1137. $attr->smartSearchCondition($id, $nr + 1, $path, $query, $destAlias, $value, $mode);
  1138. }
  1139. }
  1140. else
  1141. {
  1142. $this->searchCondition($query, $ownerAlias, $value, $mode);
  1143. }
  1144. }
  1145. /**
  1146. * Creates a searchcondition for the field,
  1147. * was once part of searchCondition, however,
  1148. * searchcondition() also immediately adds the search condition.
  1149. *
  1150. * @param atkQuery $query The query object where the search condition should be placed on
  1151. * @param String $table The name of the table in which this attribute
  1152. * is stored
  1153. * @param mixed $value The value the user has entered in the searchbox
  1154. * @param String $searchmode The searchmode to use. This can be any one
  1155. * of the supported modes, as returned by this
  1156. * attribute's getSearchModes() method.
  1157. * @param string $fieldaliasprefix The prefix for the field
  1158. * @return String The searchcondition to use.
  1159. */
  1160. function getSearchCondition(&$query, $table, $value, $searchmode, $fieldaliasprefix='')
  1161. {
  1162. if (!$this->createDestination()) return;
  1163. if (is_array($value))
  1164. {
  1165. foreach ($this->m_listColumns as $attr)
  1166. {
  1167. $attrValue = $value[$attr];
  1168. if (!empty($attrValue))
  1169. {
  1170. $p_attrib = &$this->m_destInstance->m_attribList[$attr];
  1171. if (!$p_attrib == NULL)
  1172. {
  1173. $p_attrib->searchCondition($query, $this->fieldName(), $attrValue, $this->getChildSearchMode($searchmode, $p_attrib->formName()));
  1174. }
  1175. }
  1176. }
  1177. if (isset($value[$this->fieldName()]))
  1178. {
  1179. $value = $value[$this->fieldName()];
  1180. }
  1181. }
  1182. if (empty($value))
  1183. {
  1184. return '';
  1185. }
  1186. else if (!$this->hasFlag(AF_LARGE) && !$this->hasFlag(AF_RELATION_AUTOCOMPLETE))
  1187. {
  1188. // We only support 'exact' matches.
  1189. // But you can select more than one value, which we search using the IN() statement,
  1190. // which should work in any ansi compatible database.
  1191. 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.
  1192. {
  1193. $value = array($value);
  1194. }
  1195. if (count($value)==1) // exactly one value
  1196. {
  1197. if ($value[0] == "__NONE__")
  1198. {
  1199. return $query->nullCondition($table.".".$this->fieldName(), true);
  1200. }
  1201. elseif ($value[0] != "")
  1202. {
  1203. return $query->exactCondition($table.".".$this->fieldName(),$this->escapeSQL($value[0]));
  1204. }
  1205. }
  1206. else // search for more values using IN()
  1207. {
  1208. return $table.".".$this->fieldName()." IN ('".implode("','",$value)."')";
  1209. }
  1210. }
  1211. else // AF_LARGE || AF_RELATION_AUTOCOMPLETE
  1212. {
  1213. // If we have a descriptor with multiple fields, use CONCAT
  1214. $attribs = $this->m_destInstance->descriptorFields();
  1215. $alias = $fieldaliasprefix . $this->fieldName();
  1216. if(count($attribs)>1)
  1217. {
  1218. $searchcondition = $this->getConcatFilter($value,$alias);
  1219. }
  1220. else
  1221. {
  1222. // ask the destination node for it's search condition
  1223. $searchcondition = $this->m_destInstance->getSearchCondition($query, $alias, $fieldaliasprefix, $value, $this->getChildSearchMode($searchmode, $this->formName()));
  1224. }
  1225. return $searchcondition;
  1226. }
  1227. }
  1228. /**
  1229. * Adds this attribute to database queries.
  1230. *
  1231. * Database queries (select, insert and update) are passed to this method
  1232. * so the attribute can 'hook' itself into the query.
  1233. *
  1234. * @param atkQuery $query The SQL query object
  1235. * @param String $tablename The name of the table of this attribute
  1236. * @param String $fieldaliasprefix Prefix to use in front of the alias
  1237. * in the query.
  1238. * @param Array $rec The record that contains the value of this attribute.
  1239. * @param int $level Recursion level if relations point to eachother, an
  1240. * endless loop could occur if they keep loading
  1241. * eachothers data. The $level is used to detect this
  1242. * loop. If overriden in a derived class, any subcall to
  1243. * an addToQuery method should pass the $level+1.
  1244. * @param String $mode Indicates what kind of query is being processing:
  1245. * This can be any action performed on a node (edit,
  1246. * add, etc) Mind you that "add" and "update" are the
  1247. * actions that store something in the database,
  1248. * whereas the rest are probably select queries.
  1249. */
  1250. function addToQuery(&$query, $tablename="", $fieldaliasprefix="", $rec="", $level=0, $mode="")
  1251. {
  1252. if ($this->hasFlag(AF_MANYTOONE_LAZY))
  1253. {
  1254. parent::addToQuery($query, $tablename, $fieldaliasprefix, $rec, $level, $mode);
  1255. return;
  1256. }
  1257. if ($this->createDestination())
  1258. {
  1259. if ($mode != "update" && $mode != "add")
  1260. {
  1261. $alias = $fieldaliasprefix . $this->fieldName();
  1262. $query->addJoin($this->m_destInstance->m_table,
  1263. $alias,
  1264. $this->getJoinCondition($query, $tablename, $alias),
  1265. $this->m_leftjoin);
  1266. $this->m_destInstance->addToQuery($query, $alias, $level+1, false, $mode, $this->m_listColumns);
  1267. }
  1268. else
  1269. {
  1270. for ($i=0, $_i=count($this->m_refKey); $i<$_i; $i++)
  1271. {
  1272. if ($rec[$this->fieldName()]===NULL)
  1273. {
  1274. $query->addField($this->m_refKey[$i],"NULL","","",false);
  1275. }
  1276. else
  1277. {
  1278. $value = $rec[$this->fieldName()];
  1279. if (is_array($value))
  1280. {
  1281. $fk = &$this->m_destInstance->getAttribute($this->m_destInstance->m_primaryKey[$i]);
  1282. $value = $fk->value2db($value);
  1283. }
  1284. $query->addField($this->m_refKey[$i],$value,"","",!$this->hasFlag(AF_NO_QUOTES));
  1285. }
  1286. }
  1287. }
  1288. }
  1289. }
  1290. /**
  1291. * Retrieve detail records from the database.
  1292. *
  1293. * Called by the framework to load the detail records.
  1294. *
  1295. * @param atkDb $db The database used by the node.
  1296. * @param array $record The master record
  1297. * @param String $mode The mode for loading (admin, select, copy, etc)
  1298. *
  1299. * @return array Recordset containing detailrecords, or NULL if no detail
  1300. * records are present. Note: when $mode is edit, this
  1301. * method will always return NULL. This is a framework
  1302. * optimization because in edit pages, the records are
  1303. * loaded on the fly.
  1304. */
  1305. function load(&$db, $record, $mode)
  1306. {
  1307. return $this->_getSelectedRecord($record, $mode);
  1308. }
  1309. /**
  1310. * Determine the load type of this attribute.
  1311. *
  1312. * With this method, the attribute tells the framework whether it wants
  1313. * to be loaded in the main query (addToQuery) or whether the attribute
  1314. * has its own load() implementation.
  1315. * For the atkOneToOneRelation, this depends on the presence of the
  1316. * AF_ONETOONE_LAZY flag.
  1317. *
  1318. * Framework method. It should not be necesary to call this method
  1319. * directly.
  1320. *
  1321. * @param String $mode The type of load (view,admin,edit etc)
  1322. *
  1323. * @return int Bitmask containing information about load requirements.
  1324. * POSTLOAD|ADDTOQUERY when AF_ONETOONE_LAZY is set.
  1325. * ADDTOQUERY when AF_ONETOONE_LAZY is not set.
  1326. */
  1327. function loadType($mode)
  1328. {
  1329. if (isset($this->m_loadType[$mode]) && $this->m_loadType[$mode] !== null)
  1330. return $this->m_loadType[$mode];
  1331. else if (isset($this->m_loadType[null]) && $this->m_loadType[null] !== null)
  1332. return $this->m_loadType[null];
  1333. // Default backwardscompatible behaviour:
  1334. else if ($this->hasFlag(AF_MANYTOONE_LAZY))
  1335. return POSTLOAD|ADDTOQUERY;
  1336. else
  1337. return ADDTOQUERY;
  1338. }
  1339. /**
  1340. * Validate if the record we are referring to really exists.
  1341. *
  1342. * @param array $record
  1343. * @param string $mode
  1344. */
  1345. function validate(&$record, $mode)
  1346. {
  1347. $sessionmanager = atkGetSessionManager();
  1348. if ($sessionmanager) $storetype = $sessionmanager->stackVar('atkstore');
  1349. if ($storetype!=='session' && !$this->_isSelectableRecord($record))
  1350. {
  1351. triggerError($record, $this->fieldName(), 'error_integrity_violation');
  1352. }
  1353. }
  1354. /**
  1355. * Check if two records have the same value for this attribute
  1356. *
  1357. * @param array $recA Record A
  1358. * @param array $recB Record B
  1359. * @return boolean to indicate if the records are equal
  1360. */
  1361. function equal($recA, $recB)
  1362. {
  1363. if ($this->createDestination())
  1364. {
  1365. return (($recA[$this->fieldName()][$this->m_destInstance->primaryKeyField()]
  1366. ==
  1367. $recB[$this->fieldName()][$this->m_destInstance->primaryKeyField()])
  1368. ||
  1369. ($this->isEmpty($recA)&&$this->isEmpty($recB)));
  1370. // we must also check empty values, because empty values need not necessarily
  1371. // be equal (can be "", NULL or 0.
  1372. }
  1373. return false;
  1374. }
  1375. /**
  1376. * Return the database field type of the attribute.
  1377. *
  1378. * Note that the type returned is a 'generic' type. Each database
  1379. * vendor might have his own types, therefor, the type should be
  1380. * converted to a database specific type using $db->fieldType().
  1381. *
  1382. * If the type was read from the table metadata, that value will
  1383. * be used. Else, th…

Large files files are truncated, but you can click here to view the full file