PageRenderTime 54ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Adapto/Relation/ManyToMany.php

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