PageRenderTime 47ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/relations/class.atkmanytomanyrelation.inc

https://github.com/ibuildingsnl/ATK
PHP | 885 lines | 445 code | 88 blank | 352 comment | 63 complexity | 049c89996931d074cb3b37ccf4044997 MD5 | raw file
Possible License(s): LGPL-2.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, LGPL-3.0
  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-2005 Ibuildings.nl BV
  12. * @copyright (c)2000-2005 Ivo Jansch
  13. * @license http://www.achievo.org/atk/licensing ATK Open Source License
  14. *
  15. * @version $Revision: 6323 $
  16. * $Id$
  17. */
  18. atkimport("atk.relations.atkrelation");
  19. /**
  20. * Many to many relation. Should not be used directly.
  21. *
  22. * This class is used as base class for special kinds of manytomany
  23. * relations, like the manyboolrelation. Note that most many-to-many
  24. * relationships can be normalized to a combination of one-to-many and
  25. * many-to-one relations.
  26. *
  27. * @todo Improve multi-field support. For example setOwnerFields with multiple fields
  28. * doesn't work properly at the moment. But it seems more code does not take
  29. * multi-field support into account.
  30. *
  31. * @abstract
  32. * @author Ivo Jansch <ivo@achievo.org>
  33. * @package atk
  34. * @subpackage relations
  35. *
  36. */
  37. class atkManyToManyRelation extends atkRelation
  38. {
  39. var $m_localKey = "";
  40. var $m_remoteKey = "";
  41. var $m_link = "";
  42. var $m_linkInstance = NULL;
  43. var $m_store_deletion_filter = "";
  44. var $m_localFilter = NULL;
  45. protected $m_ownerFields = null;
  46. protected $m_limit;
  47. private $m_selectableRecordsCache = array();
  48. private $m_selectableRecordCountCache = array();
  49. /**
  50. * Constructor
  51. * @param String $name The name of the relation
  52. * @param String $link The full name of the node that is used as
  53. * intermediairy node. The intermediairy node is
  54. * assumed to have 2 attributes that are named
  55. * after the nodes at both ends of the relation.
  56. * For example, if node 'project' has a M2M relation
  57. * with 'activity', then the intermediairy node
  58. * 'project_activity' is assumed to have an attribute
  59. * named 'project' and one that is named 'activity'.
  60. * You can set your own keys by calling setLocalKey()
  61. * and setRemoteKey()
  62. * @param String $destination The full name of the node that is the other
  63. * end of the relation.
  64. * @param int $flags Flags for the relation.
  65. */
  66. function atkManyToManyRelation($name, $link, $destination, $flags=0)
  67. {
  68. $this->m_link = $link;
  69. $this->atkRelation($name, $destination, $flags|AF_CASCADE_DELETE|AF_NO_SORT);
  70. }
  71. /**
  72. * Returns the selectable records. Checks for an override in the owner instance
  73. * with name <attribname>_selection.
  74. *
  75. * @param array $record
  76. * @param string $mode
  77. * @param bool $force
  78. *
  79. * @return array
  80. */
  81. function _getSelectableRecords($record=array(), $mode="", $force=false)
  82. {
  83. $method = $this->fieldName()."_selection";
  84. if (method_exists($this->m_ownerInstance, $method))
  85. return $this->m_ownerInstance->$method($record, $mode);
  86. else return $this->getSelectableRecords($record, $mode, $force);
  87. }
  88. /**
  89. * Parse destination filter and return the result.
  90. *
  91. * @param array $record record
  92. *
  93. * @return string parsed filter
  94. */
  95. private function parseDestinationFilter($record)
  96. {
  97. $filter = "";
  98. if ($this->m_destinationFilter!="")
  99. {
  100. $filter = $this->parseFilter($this->m_destinationFilter, $record);
  101. }
  102. return $filter;
  103. }
  104. /**
  105. * Returns the selectable record count.
  106. *
  107. * @param array $record
  108. * @param string $mode
  109. *
  110. * @return int
  111. */
  112. protected function _getSelectableRecordCount($record=array(), $mode="")
  113. {
  114. $method = $this->fieldName()."_selection";
  115. if (method_exists($this->m_ownerInstance, $method))
  116. return count($this->_getSelectableRecords($record, $mode));
  117. else return $this->getSelectableRecordCount($record, $mode);
  118. }
  119. /**
  120. * Returns the selectable record count. The count is cached unless the
  121. * $force parameter is set to true
  122. *
  123. * @param array $record
  124. * @param string $mode
  125. * @param boolean $force
  126. *
  127. * @return int
  128. */
  129. public function getSelectableRecordCount($record=array(), $mode='', $force=false)
  130. {
  131. if (!$this->createDestination())
  132. {
  133. return 0;
  134. }
  135. $filter = $this->parseDestinationFilter($record);
  136. $cacheKey = md5($filter);
  137. if (!array_key_exists($cacheKey, $this->m_selectableRecordCountCache) || $force)
  138. {
  139. $this->m_selectableRecordCountCache[$cacheKey] =
  140. $this->getDestination()
  141. ->select($filter)
  142. ->getRowCount();
  143. }
  144. return $this->m_selectableRecordCountCache[$cacheKey];
  145. }
  146. /**
  147. * Returns the selectable records for this relation. The records are cached
  148. * unless the $force parameter is set to true.
  149. *
  150. * @param array $record
  151. * @param string $mode
  152. * @param boolean $force
  153. *
  154. * @return array selectable records
  155. */
  156. public function getSelectableRecords($record=array(), $mode="", $force=false)
  157. {
  158. if (!$this->createDestination())
  159. {
  160. return array();
  161. }
  162. $filter = $this->parseDestinationFilter($record);
  163. $cacheKey = md5($filter);
  164. if (!array_key_exists($cacheKey, $this->m_selectableRecordsCache) || $force)
  165. {
  166. $this->m_selectableRecordsCache[$cacheKey] =
  167. $this->getDestination()
  168. ->select($filter)
  169. ->limit(is_numeric($this->m_limit) ? $this->m_limit : -1)
  170. ->includes(atk_array_merge($this->m_destInstance->descriptorFields(), $this->m_destInstance->m_primaryKey))
  171. ->getAllRows();
  172. }
  173. return $this->m_selectableRecordsCache[$cacheKey];
  174. }
  175. /**
  176. * Clears the selectable record count and records cache.
  177. */
  178. public function clearSelectableCache()
  179. {
  180. $this->m_selectableRecordCountCache = array();
  181. $this->m_selectableRecordsCache = array();
  182. }
  183. /**
  184. * Returns the primary keys of the currently selected records retrieved
  185. * from the given record.
  186. *
  187. * @param array $record current record
  188. *
  189. * @return array list of selected record keys
  190. */
  191. function getSelectedRecords($record)
  192. {
  193. $keys = array();
  194. if (isset($record[$this->fieldName()]))
  195. {
  196. for ($i = 0; $i < count($record[$this->fieldName()]); $i++)
  197. {
  198. if (is_array($record[$this->fieldName()][$i][$this->getRemoteKey()]))
  199. {
  200. $key = $this->m_destInstance->primaryKey($record[$this->fieldName()][$i][$this->getRemoteKey()]);
  201. }
  202. else
  203. {
  204. $key = $this->m_destInstance->primaryKey(array($this->m_destInstance->primaryKeyField()=>$record[$this->fieldName()][$i][$this->getRemoteKey()]));
  205. }
  206. $keys[] = $key;
  207. }
  208. }
  209. return $keys;
  210. }
  211. /**
  212. * Create instance of the intermediairy link node.
  213. *
  214. * If succesful, the instance is stored in the m_linkInstance member
  215. * variable.
  216. * @return boolean True if successful, false if not.
  217. */
  218. function createLink()
  219. {
  220. if ($this->m_linkInstance == NULL)
  221. {
  222. $this->m_linkInstance = &newNode($this->m_link);
  223. // Validate if destination was created succesfully
  224. if (!is_object($this->m_linkInstance))
  225. {
  226. atkerror("Relation with unknown nodetype '".$this->m_link."' (in node '".$this->m_owner."')");
  227. $this->m_linkInstance = NULL;
  228. return false;
  229. }
  230. }
  231. return true;
  232. }
  233. /**
  234. * Returns the link instance.
  235. *
  236. * The link has to be created first for this method to work.
  237. *
  238. * @return atkNode link instance
  239. */
  240. public function getLink()
  241. {
  242. return $this->m_linkInstance;
  243. }
  244. /**
  245. * Get the name of the attribute of the intermediairy node that points
  246. * to the master node.
  247. * @return String The name of the attribute.
  248. */
  249. function getLocalKey()
  250. {
  251. if ($this->m_localKey=="")
  252. {
  253. $this->m_localKey = $this->determineKeyName($this->m_owner);
  254. }
  255. return $this->m_localKey;
  256. }
  257. /**
  258. * Change the name of the attribute of the intermediairy node that points
  259. * to the master node.
  260. * @param String $attributename The name of the attribute.
  261. */
  262. function setLocalKey($attributename)
  263. {
  264. $this->m_localKey = $attributename;
  265. }
  266. /**
  267. * Get the name of the attribute of the intermediairy node that points
  268. * to the node on the other side of the relation.
  269. * @return String The name of the attribute.
  270. */
  271. function getRemoteKey()
  272. {
  273. $this->createDestination();
  274. if ($this->m_remoteKey=="")
  275. {
  276. list($module, $nodename) = explode(".", $this->m_destination);
  277. $this->m_remoteKey = $this->determineKeyName($nodename);
  278. }
  279. return $this->m_remoteKey;
  280. }
  281. /**
  282. * Sets the owner fields in the owner instance. The owner fields are
  283. * the attribute(s) of the owner instance which map to the local key
  284. * of the link node.
  285. *
  286. * @param array $ownerfields
  287. */
  288. public function setOwnerFields($ownerfields)
  289. {
  290. $this->m_ownerFields = $ownerfields;
  291. }
  292. /**
  293. * Returns the owner fields. The owners fields are the attribute(s)
  294. * of the owner instance which map to the local key of the link node.
  295. *
  296. * @return array owner fields
  297. */
  298. public function getOwnerFields()
  299. {
  300. if (is_array($this->m_ownerFields) && count($this->m_ownerFields)>0)
  301. {
  302. return $this->m_ownerFields;
  303. }
  304. return $this->m_ownerInstance->m_primaryKey;
  305. }
  306. /**
  307. * Determine the name of the foreign key based on the name of the
  308. * relation.
  309. *
  310. * @param String $name the name of the relation
  311. * @return the probable name of the foreign key
  312. */
  313. function determineKeyName($name)
  314. {
  315. if ($this->createLink())
  316. {
  317. if (isset($this->m_linkInstance->m_attribList[$name]))
  318. {
  319. // there's an attribute with the same name as the role.
  320. return $name;
  321. }
  322. else
  323. {
  324. // find out if there's a field with the same name with _id appended to it
  325. if (isset($this->m_linkInstance->m_attribList[$name."_id"]))
  326. {
  327. return $name."_id";
  328. }
  329. }
  330. }
  331. return $name;
  332. }
  333. /**
  334. * Change the name of the attribute of the intermediairy node that points
  335. * to the node on the other side of the relation.
  336. * @param String $attributename The name of the attribute.
  337. */
  338. function setRemoteKey($attributename)
  339. {
  340. $this->m_remoteKey = $attributename;
  341. }
  342. /**
  343. * Returns a displayable string for this value.
  344. *
  345. * @param array $record The record that holds the value for this attribute
  346. * @param String $mode The display mode ("view" for viewpages, or "list"
  347. * for displaying in recordlists, "edit" for
  348. * displaying in editscreens, "add" for displaying in
  349. * add screens. "csv" for csv files. Applications can
  350. * use additional modes.
  351. * @return a displayable string for this value
  352. */
  353. function display($record, $mode="")
  354. {
  355. if (!in_array($mode, array("csv", "plain"))) { $result = "&nbsp;"; } else { $result=''; }
  356. if ($this->createDestination() && atk_value_in_array($record[$this->fieldName()]))
  357. {
  358. $recordset = array();
  359. $remotekey = $this->getRemoteKey();
  360. for ($i=0;$i<count($record[$this->fieldName()]);$i++)
  361. {
  362. if(!is_array($record[$this->fieldName()][$i][$remotekey]))
  363. {
  364. $selector = $this->m_destInstance->m_table.".".$this->m_destInstance->primaryKeyField()."='".$record[$this->fieldName()][$i][$remotekey]."'";
  365. list($rec) = $this->m_destInstance->selectDb($selector,"","","",$this->m_destInstance->descriptorFields());
  366. $recordset[] = $this->m_destInstance->descriptor($rec);
  367. }
  368. else
  369. {
  370. $recordset[] = $this->m_destInstance->descriptor($record[$this->fieldName()][$i][$remotekey]);
  371. }
  372. }
  373. if (!in_array($mode, array("csv", "plain")))
  374. {
  375. $result = "<ul><li>".implode("<li>",$recordset)."</ul>";
  376. }
  377. else
  378. {
  379. $result = implode(", ",$recordset);
  380. }
  381. }
  382. return $result;
  383. }
  384. /**
  385. * Dummy function
  386. *
  387. * @param array $record The record that holds the value for this attribute.
  388. * @param String $fieldprefix The fieldprefix to put in front of the name
  389. * of any html form element for this attribute.
  390. * @param String $mode The mode we're in ('add' or 'edit')
  391. * @return String A piece of htmlcode for editing this attribute
  392. */
  393. function edit($record="", $fieldprefix="", $mode="")
  394. {
  395. }
  396. /**
  397. * Dummy function (we don't add ourselves to the query)
  398. * @param atkQuery $query The SQL query object
  399. * @param String $tablename The name of the table of this attribute
  400. * @param String $fieldaliasprefix Prefix to use in front of the alias
  401. * in the query.
  402. * @param Array $rec The record that contains the value of this attribute.
  403. * @param int $level Recursion level if relations point to eachother, an
  404. * endless loop could occur if they keep loading
  405. * eachothers data. The $level is used to detect this
  406. * loop. If overriden in a derived class, any subcall to
  407. * an addToQuery method should pass the $level+1.
  408. * @param String $mode Indicates what kind of query is being processing:
  409. * This can be any action performed on a node (edit,
  410. * add, etc) Mind you that "add" and "update" are the
  411. * actions that store something in the database,
  412. * whereas the rest are probably select queries.
  413. */
  414. function addToQuery(&$query, $tablename="", $fieldaliasprefix="", $rec, $level, $mode)
  415. {
  416. // we don't add ourselves to the query;
  417. }
  418. /**
  419. * load function
  420. * @param atkDb $notused
  421. * @param array $record
  422. */
  423. function load($notused, $record)
  424. {
  425. if ($this->createLink())
  426. {
  427. $where = $this->_getLoadWhereClause($record);
  428. $rel = &$this->m_linkInstance;
  429. return $rel->selectDb($where);
  430. }
  431. return array();
  432. }
  433. /**
  434. * Get where clause for loading the record
  435. *
  436. * @param array $record The record
  437. * @return string The where clause
  438. */
  439. function _getLoadWhereClause($record)
  440. {
  441. $whereelems = array();
  442. $localkey = $this->getLocalKey();
  443. if(!is_array($localkey)) $localkey = array($localkey);
  444. $ownerfields = $this->getOwnerFields();
  445. for ($i=0, $_i = count($localkey); $i<$_i; $i++)
  446. {
  447. $primkeyattr = &$this->m_ownerInstance->m_attribList[$ownerfields[$i]];
  448. if (!$primkeyattr->isEmpty($record))
  449. {
  450. $whereelems[] = $this->m_linkInstance->m_table.".".$localkey[$i]."='".$primkeyattr->value2db($record)."'";
  451. }
  452. }
  453. if ($this->m_localFilter != NULL)
  454. $whereelems[] = $this->m_localFilter;
  455. return "(".implode(") AND (", $whereelems).")";
  456. }
  457. /**
  458. * delete relational records..
  459. *
  460. * @param array $record The record
  461. */
  462. function delete($record)
  463. {
  464. if ($this->createLink())
  465. {
  466. $rel = &$this->m_linkInstance;
  467. $where = $this->_getLoadWhereClause($record);
  468. if($where!='')
  469. return $rel->deleteDb($where);
  470. }
  471. return false;
  472. }
  473. /**
  474. * Returns an array with the existing records indexed by their
  475. * primary key selector string.
  476. *
  477. * @param atkDb $db database instance
  478. * @param array $record record
  479. * @param string $mode mode
  480. */
  481. protected function _getExistingRecordsByKey(atkDb $db, $record, $mode)
  482. {
  483. $existingRecords = $this->load($db, $record, $mode);
  484. $existingRecordsByKey = array();
  485. foreach ($existingRecords as $existingRecord)
  486. {
  487. $existingRecordKey =
  488. is_array($existingRecord[$this->getRemoteKey()]) ?
  489. $existingRecord[$this->getRemoteKey()][$this->getDestination()->primaryKeyField()] :
  490. $existingRecord[$this->getRemoteKey()];
  491. $existingRecordsByKey[$existingRecordKey] = $existingRecord;
  492. }
  493. return $existingRecordsByKey;
  494. }
  495. /**
  496. * Extracts the selected records from the owner instance record for
  497. * this relation and index them by their primary key selector string.
  498. *
  499. * @param array $record record
  500. */
  501. protected function _extractSelectedRecordsByKey($record)
  502. {
  503. $selectedRecordsByKey = array();
  504. foreach ($record[$this->fieldName()] as $selectedRecord)
  505. {
  506. $selectedKey =
  507. is_array($selectedRecord[$this->getRemoteKey()]) ?
  508. $selectedRecord[$this->getRemoteKey()][$this->getDestination()->primaryKeyField()] :
  509. $selectedRecord[$this->getRemoteKey()];
  510. $selectedRecordsByKey[$selectedKey] = $selectedRecord;
  511. }
  512. return $selectedRecordsByKey;
  513. }
  514. /**
  515. * Delete existing link record.
  516. *
  517. * @param array $record link record
  518. */
  519. protected function _deleteRecord($record)
  520. {
  521. $selector = $this->getLink()->primaryKey($record);
  522. // append the store deletion filter (if set)
  523. if (!empty($this->m_store_deletion_filter))
  524. {
  525. $selector = "({$selector}) AND ({$this->m_store_deletion_filter})";
  526. }
  527. return $this->getLink()->deleteDb($selector);
  528. }
  529. /**
  530. * Update existing link record.
  531. *
  532. * @param array $record link record
  533. * @param int $index (new) index (0-based)
  534. */
  535. protected function _updateRecord($record, $index)
  536. {
  537. // don't do anything by default
  538. return true;
  539. }
  540. /**
  541. * Create new link record.
  542. *
  543. * @param string $selectedKey primary key selector string of destination record
  544. * @param array $selectedRecord selected destination record (might only contain the key attributes)
  545. * @param array $ownerRecord owner instance record
  546. * @param int $index (new) index (0-based)
  547. *
  548. * @return array new link record (not saved yet!)
  549. */
  550. protected function _createRecord($selectedKey, $selectedRecord, $ownerRecord, $index)
  551. {
  552. $record = array_merge($this->getLink()->initial_values(), $selectedRecord);
  553. $record[$this->getRemoteKey()] = $selectedKey;
  554. $ownerFields = $this->getOwnerFields();
  555. $localKey = $this->getLocalKey();
  556. if (is_array($localKey))
  557. {
  558. for ($j = 0; $j < count($localKey); $j++)
  559. {
  560. $locKey = $this->checkKeyDimension($ownerRecord[$ownerFields[$j]]);
  561. $record[$localKey[0]][$ownerFields[$j]] = $locKey;
  562. }
  563. }
  564. else
  565. {
  566. $locKey = $this->checkKeyDimension($ownerRecord[$ownerFields[0]]);
  567. $record[$localKey] = $locKey;
  568. }
  569. return $record;
  570. }
  571. /**
  572. * Add new link record to the database.
  573. *
  574. * @param array $record link record
  575. * @param int $index (new) index (0-based)
  576. * @param string $mode storage mode
  577. */
  578. protected function _addRecord($record, $index, $mode)
  579. {
  580. return $this->getLink()->addDb($record, true, $mode);
  581. }
  582. /**
  583. * Stores the values in the database
  584. *
  585. * @param atkDb $db database instance
  586. * @param array $record owner instance record
  587. * @param string $mode storage mode
  588. */
  589. function store($db, $record, $mode)
  590. {
  591. $this->createLink();
  592. $this->createDestination();
  593. $existingRecordsByKey = $this->_getExistingRecordsByKey($db, $record, $mode);
  594. $existingRecordsKeys = array_keys($existingRecordsByKey);
  595. $selectedRecordsByKey = $this->_extractSelectedRecordsByKey($record);
  596. $selectedRecordsKeys = array_keys($selectedRecordsByKey);
  597. // first delete the existing records that aren't selected anymore
  598. $deleteKeys = array_diff($existingRecordsKeys, $selectedRecordsKeys);
  599. foreach ($deleteKeys as $deleteKey)
  600. {
  601. if (!$this->_deleteRecord($existingRecordsByKey[$deleteKey]))
  602. {
  603. return false;
  604. }
  605. }
  606. // then add new or update existing records
  607. $index = 0;
  608. foreach ($selectedRecordsByKey as $selectedKey => $selectedRecord)
  609. {
  610. if (isset($existingRecordsByKey[$selectedKey]))
  611. {
  612. if (!$this->_updateRecord($existingRecordsByKey[$selectedKey], $index))
  613. {
  614. return false;
  615. }
  616. }
  617. else
  618. {
  619. $newRecord = $this->_createRecord($selectedKey, $selectedRecord, $record, $index);
  620. if (!$this->_addRecord($newRecord, $index, $mode))
  621. {
  622. return false;
  623. }
  624. }
  625. $index++;
  626. }
  627. return true;
  628. }
  629. /**
  630. * Check if the attribute is empty
  631. *
  632. * @param array $postvars
  633. * @return true if it's empty
  634. */
  635. function isEmpty($postvars)
  636. {
  637. return (!is_array($postvars[$this->fieldName()]) || count($postvars[$this->fieldName()])==0);
  638. }
  639. /**
  640. * Returns a piece of html code for hiding this attribute in an HTML form,
  641. * while still posting its value. (<input type="hidden">)
  642. *
  643. * @param array $record The record that holds the value for this attribute
  644. * @param String $fieldprefix The fieldprefix to put in front of the name
  645. * of any html form element for this attribute.
  646. * @return String A piece of htmlcode with hidden form elements that post
  647. * This attribute's value without showing it.
  648. */
  649. function hide($record="", $fieldprefix="")
  650. {
  651. $result = "";
  652. if(is_array(atkArrayNvl($record,$this->fieldName())) && $this->createDestination())
  653. {
  654. $ownerFields = $this->getOwnerFields();
  655. for($i=0,$_i=count($record[$this->fieldName()]);$i<$_i;$i++)
  656. {
  657. if(atkArrayNvl($record[$this->fieldName()][$i],$this->getLocalKey()))
  658. $result .= '<input type="hidden" name="'.$fieldprefix.$this->formName().
  659. '['.$i.']['.$this->getLocalKey().']" value="'.
  660. $this->checkKeyDimension($record[$this->fieldName()][$i][$this->getLocalKey()],
  661. $ownerFields[0]).'">';
  662. if(atkArrayNvl($record[$this->fieldName()][$i],$this->getRemoteKey()))
  663. $result .= '<input type="hidden" name="'.$fieldprefix.$this->formName().
  664. '['.$i.']['.$this->getRemoteKey().']" value="'.
  665. $this->checkKeyDimension($record[$this->fieldName()][$i][$this->getRemoteKey()],
  666. $this->m_destInstance->primaryKeyField()).'">';
  667. }
  668. }
  669. return $result;
  670. }
  671. /**
  672. * Returns a piece of html code that can be used in a form to search
  673. *
  674. * @param array $record Array with values
  675. * @param boolean $extended if set to false, a simple search input is
  676. * returned for use in the searchbar of the
  677. * recordlist. If set to true, a more extended
  678. * search may be returned for the 'extended'
  679. * search page. The atkAttribute does not
  680. * make a difference for $extended is true, but
  681. * derived attributes may reimplement this.
  682. * @param string $fieldprefix The fieldprefix of this attribute's HTML element.
  683. *
  684. * @return string Piece of html code
  685. */
  686. function search($record="", $extended=false, $fieldprefix="")
  687. {
  688. $this->createDestination();
  689. // now select all records
  690. $recordset = $this->m_destInstance->selectDb("","","","*",atk_array_merge($this->m_destInstance->descriptorFields(),$this->m_destInstance->m_primaryKey));
  691. $result = '<select ';
  692. if ($extended)
  693. {
  694. $result.='multiple="multiple" size="'.min(5,count($recordset)+1).'"';
  695. }
  696. $result.='name="'.$this->getSearchFieldName($fieldprefix).'[]">';
  697. $pkfield = $this->m_destInstance->primaryKeyField();
  698. $result.= '<option value="">'.atktext("search_all", "atk").'</option>';
  699. for ($i=0;$i<count($recordset);$i++)
  700. {
  701. $pk = $recordset[$i][$pkfield];
  702. if (atk_in_array($pk, $record[$this->fieldName()])) $sel = ' selected="selected"'; else $sel = "";
  703. $result.= '<option value="'.$pk.'"'.$sel.'>'.$this->m_destInstance->descriptor($recordset[$i]).'</option>';
  704. }
  705. $result.='</select>';
  706. return $result;
  707. }
  708. /**
  709. * Creates an search condition for a given search value
  710. *
  711. * @param atkQuery $query The query to which the condition will be added.
  712. * @param String $table The name of the table in which this attribute
  713. * is stored
  714. * @param mixed $value The value the user has entered in the searchbox
  715. * @param String $searchmode The searchmode to use. This can be any one
  716. * of the supported modes, as returned by this
  717. * attribute's getSearchModes() method.
  718. * @param string $fieldaliasprefix optional prefix for the fieldalias in the table
  719. */
  720. function searchCondition(&$query, $table, $value, $searchmode, $fieldaliasprefix='')
  721. {
  722. $ownerFields = $this->getOwnerFields();
  723. // We only support 'exact' matches.
  724. // But you can select more than one value, which we search using the IN() statement,
  725. // which should work in any ansi compatible database.
  726. if (is_array($value) && count($value)>0 && $value[0]!="") // This last condition is for when the user selected the 'search all' option, in which case, we don't add conditions at all.
  727. {
  728. $this->createLink();
  729. $query->addJoin($this->m_linkInstance->m_table, $this->fieldName(), $table.".".$ownerFields[0]."=".$this->fieldName().".".$this->getLocalKey(),FALSE );
  730. $query->setDistinct(TRUE);
  731. if (count($value)==1) // exactly one value
  732. {
  733. $query->addSearchCondition($query->exactCondition($this->fieldName().".".$this->getRemoteKey(),$this->escapeSQL($value[0])));
  734. }
  735. else // search for more values using IN()
  736. {
  737. $query->addSearchCondition($this->fieldName().".".$this->getRemoteKey()." IN ('".implode("','",$value)."')");
  738. }
  739. }
  740. }
  741. /**
  742. * Checks if a key is not an array
  743. * @param string $key field containing the key values
  744. * @param string $field field to return if an array
  745. * @return value of $field
  746. */
  747. function checkKeyDimension($key, $field="id")
  748. {
  749. if (is_array($key))
  750. {
  751. return $key[$field];
  752. }
  753. return $key;
  754. }
  755. /**
  756. * Fetch value. If nothing selected, return empty array instead
  757. * of nothing.
  758. *
  759. * @param array $postvars
  760. */
  761. function fetchValue($postvars)
  762. {
  763. $value = parent::fetchValue($postvars);
  764. return $value == NULL ? array() : $value;
  765. }
  766. /**
  767. * Function adds a custom filter that is used when deleting items during the store() function.
  768. *
  769. * Example:
  770. * Normally the delete function would do something like this:
  771. *
  772. * DELETE FROM phase WHERE phase.template NOT IN (1,2,3)
  773. *
  774. * If the template field is NULL, although it is not specified in the NOT IN (1,2,3), it will not be deleted.
  775. * An extra check can be added just in case the template value is not NULL but 0 or '' (which would delete the phase).
  776. *
  777. * @param String $filter The filter that is used when deleting records in the store function.
  778. * @return none
  779. */
  780. function setStoreDeletionFilter($filter)
  781. {
  782. $this->m_store_deletion_filter = $filter;
  783. }
  784. /**
  785. * Local filter is used to only show values that are once selected
  786. * that comply with the local filter. A local filter is also automatically
  787. * set as store deletion filter.
  788. *
  789. * @param string $filter filter
  790. */
  791. function setLocalFilter($filter)
  792. {
  793. $this->setStoreDeletionFilter($filter);
  794. $this->m_localFilter = $filter;
  795. }
  796. }
  797. ?>