PageRenderTime 55ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Adapto/Relation/OneToOne.php

http://github.com/egeniq/adapto
PHP | 1151 lines | 532 code | 94 blank | 525 comment | 254 complexity | e85b71f22b55dd059fdeedf64576e3ae MD5 | raw file

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 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-2004 Ivo Jansch
  12. * @license http://www.achievo.org/atk/licensing ATK Open Source License
  13. *
  14. */
  15. /**
  16. * flags specific for Adapto_Relation_OneToOne
  17. */
  18. /**
  19. * Override the default no add flag
  20. */
  21. define("AF_ONETOONE_ADD", AF_SPECIFIC_1);
  22. /**
  23. * Enable error notifications / triggers
  24. */
  25. define("AF_ONETOONE_ERROR", AF_SPECIFIC_2);
  26. /**
  27. * Invisibly integrate a onetoonerelation as if the fields where part of the current entity.
  28. * If the relation is integrated, no divider is drawn, and the section heading is suppressed.
  29. * (Integration does not affect the way data is stored or manipulated, only how it is displayed.)
  30. */
  31. define("AF_ONETOONE_INTEGRATE", AF_SPECIFIC_3);
  32. /**
  33. * Use lazy loading instead of query addition.
  34. */
  35. define("AF_ONETOONE_LAZY", AF_SPECIFIC_4);
  36. /**
  37. * Respects tab/sections that have been assigned to this attribute instead of using the
  38. * tabs assigned for the attributes in the destination entity. This flag is only useful in
  39. * integration mode.
  40. */
  41. define("AF_ONETOONE_RESPECT_TABS", AF_SPECIFIC_5);
  42. /**
  43. * @internal Include the base class.
  44. */
  45. userelation("atkrelation");
  46. /**
  47. * Implementation of one-to-one relationships.
  48. *
  49. * An Adapto_Relation_OneToOne defines a relation between two tables where there
  50. * is one record in the first table that belongs to one record in the
  51. * second table.
  52. *
  53. * When editing a one-to-one relation, the form for the destination record
  54. * is embedded in the form of the master record. When using the flag
  55. * AF_ONETOONE_INTEGRATE, this is done transparantly so the user does not
  56. * even notice that the data he's editing comes from 2 separate tables.
  57. *
  58. * @author ijansch
  59. * @package adapto
  60. * @subpackage relations
  61. *
  62. */
  63. class Adapto_Relation_OneToOne extends Adapto_Relation
  64. {
  65. /**
  66. * The name of the referential key attribute in the target entity.
  67. * @access private
  68. * @var String
  69. */
  70. public $m_refKey = ""; // defaulted to public
  71. /**
  72. * Default Constructor
  73. *
  74. * The Adapto_Relation_OneToOne supports two configurations:
  75. * - Master mode: The current entity is considered the master, and the
  76. * referential key pointing to the master record is in the
  77. * destination entity.
  78. * - Slave mode: The current entity is considered the child, and the
  79. * referential key pointing to the master record is in the
  80. * current entity.
  81. * The mode to use is detected automatically based on the value of the
  82. * $refKey parameter.
  83. *
  84. * <b>Example:</b>
  85. * <code>
  86. * $this->add(new Adapto_Relation_OneToOne("child", "mymod.childentity", "parent_id"));
  87. * </code>
  88. *
  89. * @param String $name The unique name of the attribute. In slave mode,
  90. * this corresponds to the foreign key field in the
  91. * database table. (The name is also used as the section
  92. * heading.)
  93. * @param String $destination the destination entity (in module.entityname
  94. * notation)
  95. * @param String $refKey In master mode, this specifies the foreign key
  96. * field from the destination entity that points to
  97. * the master record. In slave mode, this parameter
  98. * should be empty.
  99. * @param int $flags Attribute flags that influence this attributes'
  100. * behavior.
  101. */
  102. public function __construct($name, $destination, $refKey = "", $flags = 0)
  103. {
  104. if ($flags & AF_ONETOONE_ADD != AF_ONETOONE_ADD)
  105. $flags |= AF_NO_ADD;
  106. parent::__construct($name, $destination, $flags | AF_ONETOONE_LAZY);
  107. $this->m_refKey = $refKey;
  108. }
  109. /**
  110. * Returns a displayable string for this value, to be used in HTML pages.
  111. *
  112. * The Adapto_Relation_OneToOne displays all values from the destination
  113. * records in "view" mode. In "list" mode, the record descriptor of the
  114. * target record is displayed.
  115. *
  116. * @param array $record The record that holds the value for this attribute
  117. * @param String $mode The display mode ("view" for viewpages, or "list"
  118. * for displaying in recordlists)
  119. * @return String HTML String
  120. */
  121. function display($record, $mode = "list")
  122. {
  123. if ($mode == 'view') {
  124. return null;
  125. }
  126. $myrecord = $record[$this->fieldName()];
  127. if ($this->createDestination() && is_array($myrecord)) {
  128. $result = $this->m_destInstance->descriptor($myrecord);
  129. } else {
  130. $result = $this->text('none');
  131. }
  132. return $result;
  133. }
  134. /**
  135. * Returns a piece of html code that can be used in a form to edit this
  136. * attribute's value.
  137. *
  138. * Because of the AF_INTEGRATE feature, the edit() method has a void
  139. * implementation. The actual edit code is handled by addToEditArray().
  140. */
  141. function edit()
  142. {
  143. }
  144. /**
  145. * Set the initial values of this attribute
  146. *
  147. * @return array Array with initial values
  148. */
  149. function initialValue()
  150. {
  151. if ($this->m_initialValue !== null)
  152. return parent::initialValue();
  153. if ($this->createDestination()) {
  154. return $this->m_destInstance->initial_values();
  155. }
  156. return null;
  157. }
  158. /**
  159. * Adds this attribute to database queries.
  160. *
  161. * Database queries (select, insert and update) are passed to this method
  162. * so the attribute can 'hook' itself into the query.
  163. *
  164. * Framework method. It should not be necessary to call this method
  165. * directly. This implementation performs a join to retrieve the
  166. * target records' data, unless AF_ONETOONE_LAZY is set, in which case
  167. * loading is delayed and performed later using the load() method.
  168. * For update and insert queries, this method does nothing. These are
  169. * handled by the store() method.
  170. *
  171. * @param atkQuery $query The SQL query object
  172. * @param String $tablename The name of the table of this attribute
  173. * @param String $fieldaliasprefix Prefix to use in front of the alias
  174. * in the query.
  175. * @param Array $rec The record that contains the value of this attribute.
  176. * @param int $level Recursion level if relations point to eachother, an
  177. * endless loop could occur if they keep loading
  178. * eachothers data. The $level is used to detect this
  179. * loop. If overriden in a derived class, any subcall to
  180. * an addToQuery method should pass the $level+1.
  181. * @param String $mode Indicates what kind of query is being processing:
  182. * This can be any action performed on an entity (edit,
  183. * add, etc) Mind you that "add" and "update" are the
  184. * actions that store something in the database,
  185. * whereas the rest are probably select queries.
  186. */
  187. function addToQuery(&$query, $tablename = "", $fieldaliasprefix = "", $rec = "", $level = 0, $mode = "")
  188. {
  189. if ($this->createDestination()) {
  190. if ($mode != "update" && $mode != "add") {
  191. if ($this->hasFlag(AF_ONETOONE_LAZY)) {
  192. if ($this->m_refKey == "") {
  193. return parent::addToQuery($query, $tablename, $fieldaliasprefix, $rec, $level, $mode);
  194. }
  195. }
  196. if ($tablename != "")
  197. $tablename .= ".";
  198. if ($this->m_refKey != "") {
  199. // Foreign key is in the destination entity.
  200. $condition = $tablename . $this->m_ownerInstance->m_primaryKey[0] . "=" . $fieldaliasprefix . $this->fieldName() . "." . $this->m_refKey;
  201. } else {
  202. // Foreign key is in the source entity
  203. $condition = $tablename . $this->fieldName() . "=" . $fieldaliasprefix . $this->fieldName() . "." . $this->m_destInstance->m_primaryKey[0];
  204. }
  205. $condition .= $this->getDestinationFilterCondition($fieldaliasprefix);
  206. $query->addJoin($this->m_destInstance->m_table, $fieldaliasprefix . $this->fieldName(), $condition, true, $mode);
  207. // we pass true as the last param to addToQuery, because we need all fields..
  208. $this->m_destInstance->addToQuery($query, $fieldaliasprefix . $this->fieldName(), $level + 1, true, $mode);
  209. }
  210. // When storing, we don't add to the query.. we have our own store() method..
  211. // With one exception. If the foreign key is in the source entity, we also need to update
  212. // the refkey value.
  213. if ($this->m_refKey == "" && $mode == "add") {
  214. $query->addField($this->fieldName(), $rec[$this->fieldName()][$this->m_destInstance->m_primaryKey[0]], "", "", !$this->hasFlag(AF_NO_QUOTES));
  215. }
  216. }
  217. }
  218. /**
  219. * Retrieve detail records from the database.
  220. *
  221. * Called by the framework to load the detail records.
  222. *
  223. * @param atkDb $db The database used by the entity.
  224. * @param array $record The master record
  225. * @param String $mode The mode for loading (admin, select, copy, etc)
  226. *
  227. * @return array Recordset containing detailrecords, or NULL if no detail
  228. * records are present. Note: when $mode is edit, this
  229. * method will always return NULL. This is a framework
  230. * optimization because in edit pages, the records are
  231. * loaded on the fly.
  232. */
  233. function load(&$db, $record, $mode)
  234. {
  235. if ($this->createDestination()) {
  236. if ($this->m_refKey == "") {
  237. // Foreign key in owner
  238. //$condition = $this->m_destInstance->m_primaryKey[0]."=".$record[$this->fieldName()];
  239. $condition = $this->m_destInstance->m_table . '.' . $this->m_destInstance->m_primaryKey[0] . "='" . $record[$this->fieldName()] . "'";
  240. } else {
  241. // Foreign key in destination
  242. $condition = $this->m_destInstance->m_table . '.' . $this->m_refKey . "='"
  243. . $this->m_ownerInstance->m_attribList[$this->m_ownerInstance->primaryKeyField()]->value2db($record) . "'";
  244. $destfilter = $this->getDestinationFilter();
  245. if (is_string($destfilter) && $destfilter != "") {
  246. $condition .= " AND " . $this->m_destInstance->m_table . "." . $destfilter;
  247. }
  248. }
  249. $recs = $this->m_destInstance->selectDb($condition, "", "", "", "", $mode);
  250. return isset($recs[0]) ? $recs[0] : null;
  251. }
  252. }
  253. /**
  254. * Construct the filter statement for filters that are set for the
  255. * destination entity (m_destinationFilter).
  256. * @access private
  257. * @param string $fieldaliasprefix
  258. * @return String A where clause condition.
  259. */
  260. function getDestinationFilterCondition($fieldaliasprefix = "")
  261. {
  262. $condition = "";
  263. if (is_array($this->m_destinationFilter)) {
  264. for ($i = 0, $_i = count($this->m_destinationFilter); $i < $_i; $i++) {
  265. $condition .= " AND " . $fieldaliasprefix . $this->m_name . "." . $this->m_destinationFilter[$i];
  266. }
  267. } elseif ($this->m_destinationFilter != "") {
  268. $condition .= " AND " . $fieldaliasprefix . $this->m_name . "." . $this->m_destinationFilter;
  269. }
  270. return $condition;
  271. }
  272. /**
  273. * The delete method is called by the framework to inform the attribute
  274. * that the master record is deleted.
  275. *
  276. * Note that the framework only calls the method when the
  277. * AF_CASCADE_DELETE flag is set. When calling this method, the detail
  278. * record belonging to the master record is deleted.
  279. *
  280. * @param array $record The record that is deleted.
  281. * @return boolean true if cleanup was successful, false otherwise.
  282. */
  283. function delete($record)
  284. {
  285. $classname = $this->m_destination;
  286. $cache_id = $this->m_owner . "." . $this->m_name;
  287. $rel = &atkGetEntity($classname, true, $cache_id);
  288. Adapto_Util_Debugger::debug("O2O DELETE for $classname: " . $this->m_refKey . "=" . $record[$this->m_ownerInstance->primaryKeyField()]);
  289. if ($this->m_refKey != "") {
  290. // Foreign key is in the destination entity
  291. $condition = $rel->m_table . '.' . $this->m_refKey . "="
  292. . $this->m_ownerInstance->m_attribList[$this->m_ownerInstance->primaryKeyField()]->value2db($record);
  293. } else {
  294. // Foreign key is in the source entity.
  295. $condition = $rel->m_table . '.' . $rel->m_primaryKey[0] . "=" . $record[$this->fieldName()][$this->m_ownerInstance->primaryKeyField()];
  296. }
  297. return $rel->deleteDb($condition);
  298. }
  299. /**
  300. * Converts the internal attribute value to one that is understood by the
  301. * database.
  302. *
  303. * For the regular atkAttribute, this means escaping things like
  304. * quotes and slashes. Derived attributes may reimplement this for their
  305. * own conversion.
  306. * This is the exact opposite of the db2value method.
  307. *
  308. * @param array $rec The record that holds this attribute's value.
  309. * @return String The database compatible value
  310. */
  311. function db2value($rec)
  312. {
  313. // we need to pass all values to the destination entity, so it can
  314. // run it's db2value stuff over it..
  315. if ($this->hasFlag(AF_ONETOONE_LAZY) && $this->m_refKey == "") {
  316. return parent::db2value($rec);
  317. }
  318. if ($this->createDestination()) {
  319. (isset($rec[$this->fieldName()][$this->m_destInstance->primaryKeyField()])) ? $pkval = $rec[$this->fieldName()][$this->m_destInstance
  320. ->primaryKeyField()] : $pkval = NULL;
  321. if ($pkval != NULL && $pkval != "") // If primary key is not filled, there was no record, so we
  322. // should return NULL.
  323. {
  324. foreach (array_keys($this->m_destInstance->m_attribList) as $attribname) {
  325. $p_attrib = &$this->m_destInstance->m_attribList[$attribname];
  326. $rec[$this->fieldName()][$attribname] = $p_attrib->db2value($rec[$this->fieldName()]);
  327. }
  328. // also set the primkey..
  329. $rec[$this->fieldName()]["atkprimkey"] = $this->m_destInstance->primaryKey($rec[$this->fieldName()]);
  330. return $rec[$this->fieldName()];
  331. }
  332. }
  333. return NULL;
  334. }
  335. /**
  336. * Initialize this destinations attribute sizes.
  337. */
  338. function fetchMeta()
  339. {
  340. if ($this->hasFlag(AF_ONETOONE_INTEGRATE)) {
  341. $this->createDestination();
  342. $this->getDestination()->setAttribSizes();
  343. }
  344. }
  345. /**
  346. * Convert values from an HTML form posting to an internal value for
  347. * this attribute.
  348. *
  349. * This implementation uses the destination entity to fetch any field that
  350. * belongs to the other side of the relation.
  351. *
  352. * @param array $postvars The array with html posted values ($_POST, for
  353. * example) that holds this attribute's value.
  354. * @return String The internal value
  355. */
  356. function fetchValue($postvars)
  357. {
  358. // we need to pass all values to the destination entity, so it can
  359. // run it's fetchValue stuff over it..
  360. if ($this->createDestination()) {
  361. if ($postvars[$this->fieldName()] != NULL) {
  362. foreach (array_keys($this->m_destInstance->m_attribList) as $attribname) {
  363. $p_attrib = &$this->m_destInstance->m_attribList[$attribname];
  364. $postvars[$this->fieldName()][$attribname] = $p_attrib->fetchValue($postvars[$this->fieldName()]);
  365. }
  366. return $postvars[$this->fieldName()];
  367. }
  368. }
  369. }
  370. /**
  371. * Determine the storage type of this attribute.
  372. *
  373. * With this method, the attribute tells the framework whether it wants
  374. * to be stored in the main query (addToQuery) or whether the attribute
  375. * has its own store() implementation.
  376. * For the Adapto_Relation_OneToOne, the results depends on whether the
  377. * relation is used in master or slave mode.
  378. *
  379. * Framework method. It should not be necesary to call this method
  380. * directly.
  381. *
  382. * @param String $mode The type of storage ("add" or "update")
  383. *
  384. * @return int Bitmask containing information about storage requirements.
  385. * POSTSTORE when in master mode.
  386. * PRESTORE|ADDTOQUERY when in slave mode.
  387. */
  388. function storageType($mode)
  389. {
  390. // Mode specific storage type.
  391. if (isset($this->m_storageType[$mode]) && $this->m_storageType[$mode] !== null)
  392. return $this->m_storageType[$mode];
  393. // Global storage type (key null is special!)
  394. else if (isset($this->m_storageType[null]) && $this->m_storageType[null] !== null)
  395. return $this->m_storageType[null];
  396. else if ($this->m_refKey != "") {
  397. // foreign key is in destination entity, so we must store the
  398. // destination AFTER we stored the master record.
  399. return POSTSTORE;
  400. } else {
  401. // foreign key is in source entity, so we must store the
  402. // relation entity first, so we can store the foreign key
  403. // when we store the master record. To store the latter,
  404. // we must also perform an addToQuery.
  405. return PRESTORE | ADDTOQUERY;
  406. }
  407. }
  408. /**
  409. * Determine the load type of this attribute.
  410. *
  411. * With this method, the attribute tells the framework whether it wants
  412. * to be loaded in the main query (addToQuery) or whether the attribute
  413. * has its own load() implementation.
  414. * For the Adapto_Relation_OneToOne, this depends on the presence of the
  415. * AF_ONETOONE_LAZY flag.
  416. *
  417. * Framework method. It should not be necesary to call this method
  418. * directly.
  419. *
  420. * @param String $mode The type of load (view,admin,edit etc)
  421. *
  422. * @return int Bitmask containing information about load requirements.
  423. * POSTLOAD|ADDTOQUERY when AF_ONETOONE_LAZY is set.
  424. * ADDTOQUERY when AF_ONETOONE_LAZY is not set.
  425. */
  426. function loadType($mode)
  427. {
  428. if (isset($this->m_loadType[$mode]) && $this->m_loadType[$mode] !== null)
  429. return $this->m_loadType[$mode];
  430. else if (isset($this->m_loadType[null]) && $this->m_loadType[null] !== null)
  431. return $this->m_loadType[null];
  432. else if ($this->hasFlag(AF_ONETOONE_LAZY))
  433. return POSTLOAD | ADDTOQUERY;
  434. else
  435. return ADDTOQUERY;
  436. }
  437. /**
  438. * Store detail record in the database.
  439. *
  440. * @param atkDb $db The database used by the entity.
  441. * @param array $record The master record which has the detail records
  442. * embedded.
  443. * @param string $mode The mode we're in ("add", "edit", "copy")
  444. * @return boolean true if store was successful, false otherwise.
  445. */
  446. function store(&$db, &$record, $mode)
  447. {
  448. if ($this->createDestination()) {
  449. $vars = &$this->_getStoreValue($record);
  450. if ($vars["mode"] == "edit") {
  451. Adapto_Util_Debugger::debug("Updating existing one2one record");
  452. // we put the vars in the postvars, because there is information
  453. // like atkorgkey in it that is vital.
  454. // but we restore the postvars after we're done updating
  455. $oldpost = $this->m_destInstance->m_postvars;
  456. $this->m_destInstance->m_postvars = $vars;
  457. $res = $this->m_destInstance->updateDb($vars);
  458. $this->m_destInstance->m_postvars = $oldpost;
  459. return $res;
  460. } elseif ($vars["mode"] == "add" || $mode == "add" || $mode == "copy") {
  461. if (!empty($vars["atkprimkey"]) && $mode != "copy") {
  462. // destination record already exists, and we are not copying.
  463. $result = true;
  464. } else {
  465. Adapto_Util_Debugger::debug("atkonetoonerelation->store(): Adding new one2one record for mode $mode");
  466. $this->m_destInstance->preAdd($vars);
  467. $result = $this->m_destInstance->addDb($vars, true, $mode);
  468. }
  469. if ($this->m_refKey == "") {
  470. // Foreign key is in source entity, so we must update the record value with
  471. $record[$this->fieldName()][$this->m_destInstance->m_primaryKey[0]] = $vars[$this->m_destInstance->m_primaryKey[0]];
  472. }
  473. return $result;
  474. } else {
  475. Adapto_Util_Debugger::debug("atkonetoonerelation->store(): Nothing to store in one2one record");
  476. return true;
  477. }
  478. }
  479. }
  480. /**
  481. * Needs update?
  482. *
  483. * @param array $record the record
  484. * @return boolean needs update
  485. */
  486. function needsUpdate($record)
  487. {
  488. return $this->m_forceupdate
  489. || (parent::needsUpdate($record) && $this->createDestination() && !$this->m_destInstance->hasFlag(EF_READONLY));
  490. }
  491. /**
  492. * Gets the value to store for the onetoonerelation
  493. *
  494. * @param Array &$record The record to get the value from
  495. * @return mixed The value to store
  496. */
  497. function &_getStoreValue(&$record)
  498. {
  499. $vars = &$record[$this->fieldName()];
  500. if ($this->m_refKey != "") {
  501. // Foreign key is in destination entity
  502. if ($this->destinationHasRelation()) {
  503. $vars[$this->m_refKey][$this->m_ownerInstance->primaryKeyField()] = $record[$this->m_ownerInstance->primaryKeyField()];
  504. } else {
  505. //if the a onetoonerelation has no relation on the other side the m_refKey is not an array
  506. // experimental, will the next line always work?
  507. $refattr = $this->m_destInstance->getAttribute($this->m_refKey);
  508. if ($refattr->m_destination) {
  509. /**
  510. * If we have a destination, the ref key is a non-onetoone relation!
  511. * So we have to treat the record as such... this is specifically geared towards
  512. * the manytoone relation and may not work for others
  513. * A way should be found to make this work for whatever
  514. * maybe use value2db and db2value on eachother or something?
  515. */
  516. $vars[$this->m_refKey][$this->m_ownerInstance->primaryKeyField()] = $this->m_ownerInstance
  517. ->m_attribList[$this->m_ownerInstance->primaryKeyField()]->value2db($vars[$this->m_refKey]);
  518. } else {
  519. $vars[$this->m_refKey] = $this->m_ownerInstance->m_attribList[$this->m_ownerInstance->primaryKeyField()]->value2db($record);
  520. }
  521. }
  522. } else {
  523. // Foreign key is in source entity
  524. // After add, we must store the key value.
  525. }
  526. return $vars;
  527. }
  528. /**
  529. * Determine the type of the attribute on the other side.
  530. *
  531. * On the other side of a oneToOneRelation (in the destination entity),
  532. * there may be a regular atkAttribute for the referential key, or an
  533. * Adapto_Relation_OneToOne pointing back at the source. This method discovers
  534. * which of the 2 cases we are dealing with.
  535. * @return boolean True if the attribute on the other side is a
  536. * relation, false if not.
  537. */
  538. function destinationHasRelation()
  539. {
  540. if ($this->createDestination()) {
  541. if (isset($this->m_refKey) && !empty($this->m_refKey)) {
  542. // foreign key is in the destination entity.
  543. $attrib = $this->m_destInstance->m_attribList[$this->m_refKey];
  544. } else {
  545. // foreign key is in the source entity. In this case, we must check the primary key
  546. // of the target entity.
  547. $attrib = $this->m_destInstance->m_attribList[$this->m_destInstance->m_primaryKey[0]];
  548. }
  549. if (is_object($attrib) && strpos(get_class($attrib), "elation") !== false)
  550. return true;
  551. }
  552. return false;
  553. }
  554. /**
  555. * Returns a piece of html code for hiding this attribute in an HTML form,
  556. * while still posting its values. (<input type="hidden">)
  557. *
  558. * @param array $record The record that holds the value for this attribute
  559. * @param String $fieldprefix The fieldprefix to put in front of the name
  560. * of any html form element for this attribute.
  561. * @return String A piece of htmlcode with hidden form elements that post
  562. * This attribute's value without showing it.
  563. */
  564. function hide($record = "", $fieldprefix = "")
  565. {
  566. Adapto_Util_Debugger::debug("hide called for " . $this->fieldName());
  567. if ($this->createDestination()) {
  568. if ($record[$this->fieldName()] != NULL) {
  569. $myrecord = $record[$this->fieldName()];
  570. if ($myrecord[$this->m_destInstance->primaryKeyField()] == NULL) {
  571. // rec has no primkey yet, so we must add instead of update..
  572. $mode = "add";
  573. } else {
  574. $mode = "edit";
  575. $myrecord["atkprimkey"] = $this->m_destInstance->primaryKey($myrecord);
  576. }
  577. } else {
  578. $mode = "add";
  579. }
  580. $output .= '<input type="hidden" name="' . $fieldprefix . $this->fieldName() . '[mode]" value="' . $mode . '">';
  581. $forceList = decodeKeyValueSet($this->getFilter());
  582. $output .= $this->m_destInstance->hideform($mode, $myrecord, $forceList, $fieldprefix . $this->fieldName() . "_AE_");
  583. return $output;
  584. }
  585. return "";
  586. }
  587. /**
  588. * Adds the attribute's edit / hide HTML code to the edit array.
  589. *
  590. * This method is called by the entity if it wants the data needed to create
  591. * an edit form. The method is an override of atkAttribute's method,
  592. * because in the Adapto_Relation_OneToOne, we need to implement the
  593. * AF_ONETOONE_INTEGRATE feature.
  594. *
  595. * This is a framework method, it should never be called directly.
  596. *
  597. * @param String $mode the edit mode ("add" or "edit")
  598. * @param array $arr pointer to the edit array
  599. * @param array $defaults pointer to the default values array
  600. * @param array $error pointer to the error array
  601. * @param String $fieldprefix the fieldprefix
  602. */
  603. function addToEditArray($mode, &$arr, &$defaults, &$error, $fieldprefix)
  604. {
  605. /* hide */
  606. if (($mode == "edit" && $this->hasFlag(AF_HIDE_EDIT)) || ($mode == "add" && $this->hasFlag(AF_HIDE_ADD))) {
  607. /* when adding, there's nothing to hide... */
  608. if ($mode == "edit" || ($mode == "add" && !$this->isEmpty($defaults)))
  609. $arr["hide"][] = $this->hide($defaults, $fieldprefix, $mode);
  610. }
  611. /* edit */
  612. else {
  613. /* we first check if there is no edit override method, if there
  614. * is this method has the same behaviour as the atkAttribute's method
  615. */
  616. if (method_exists($this->m_ownerInstance, $this->m_name . "_edit") || $this->edit($defaults, $fieldprefix, $mode) !== NULL) {
  617. atkAttribute::addToEditArray($mode, $arr, $defaults, $error, $fieldprefix);
  618. }
  619. /* how we handle 1:1 relations normally */
  620. else {
  621. if (!$this->createDestination())
  622. return;
  623. /* readonly */
  624. if ($this->m_destInstance->hasFlag(EF_READONLY) || ($mode == "edit" && $this->hasFlag(AF_READONLY_EDIT))
  625. || ($mode == "add" && $this->hasFlag(AF_READONLY_ADD))) {
  626. $this->createDestination();
  627. $attrNames = $this->m_destInstance->getAttributeNames();
  628. foreach ($attrNames as $attrName) {
  629. $attr = &$this->m_destInstance->getAttribute($attrName);
  630. $attr->addFlag(AF_READONLY);
  631. }
  632. }
  633. /* we first check if the record doesn't already exist */
  634. if (isset($defaults[$this->fieldName()]) && !empty($defaults[$this->fieldName()])) {
  635. /* record has no primarykey yet, so we must add instead of update */
  636. $myrecord = $defaults[$this->fieldName()];
  637. if (empty($myrecord[$this->m_destInstance->primaryKeyField()])) {
  638. $mode = "add";
  639. } /* record exists! */
  640. else {
  641. $mode = "edit";
  642. $myrecord["atkprimkey"] = $this->m_destInstance->primaryKey($myrecord);
  643. }
  644. }
  645. /* record does not exist */
  646. else {
  647. $mode = "add";
  648. }
  649. /* mode */
  650. $arr["hide"][] = '<input type="hidden" name="' . $fieldprefix . $this->fieldName() . '[mode]" value="' . $mode . '">';
  651. /* add fields */
  652. $forceList = decodeKeyValueSet($this->m_destinationFilter);
  653. if ($this->m_refKey != "") {
  654. if ($this->destinationHasRelation()) {
  655. $forceList[$this->m_refKey][$this->m_ownerInstance->primaryKeyField()] = $defaults[$this->m_ownerInstance->primaryKeyField()];
  656. } else {
  657. // its possible that the destination has no relation back. In that case the refKey is just an attribute
  658. $forceList[$this->m_refKey] = $defaults[$this->m_ownerInstance->primaryKeyField()];
  659. }
  660. }
  661. $a = $this->m_destInstance->editArray($mode, $myrecord, $forceList, array(), $fieldprefix . $this->fieldName() . "_AE_", false, false);
  662. /* hidden fields */
  663. $arr["hide"] = array_merge($arr["hide"], $a["hide"]);
  664. /* editable fields, if AF_NOLABEL is specified or if there is just 1 field with the
  665. * same name as the relation we don't display a label
  666. * TODO FIXME
  667. */
  668. if (!is_array($arr['fields']))
  669. $arr['fields'] = array();
  670. if (!$this->hasFlag(AF_ONETOONE_INTEGRATE) && !$this->hasFlag(AF_NOLABEL)
  671. && !(count($a["fields"]) == 1 && $a["fields"][0]["name"] == $this->m_name)) {
  672. /* separator and name */
  673. if ($arr['fields'][count($arr['fields']) - 1]['html'] !== '-')
  674. $arr["fields"][] = array("html" => "-", "tabs" => $this->m_tabs, 'sections' => $this->getSections());
  675. $arr["fields"][] = array("line" => "<b>" . atktext($this->m_name, $this->m_ownerInstance->m_module, $this->m_ownerInstance->m_type)
  676. . "</b>", "tabs" => $this->m_tabs, 'sections' => $this->getSections());
  677. }
  678. if (is_array($a["fields"])) {
  679. // in non-integration mode we move all the fields to the one-to-one relations tabs/sections
  680. if (!$this->hasFlag(AF_ONETOONE_INTEGRATE) || $this->hasFlag(AF_ONETOONE_RESPECT_TABS)) {
  681. foreach (array_keys($a['fields']) as $key) {
  682. $a['fields'][$key]['tabs'] = $this->m_tabs;
  683. $a['fields'][$key]['sections'] = $this->getSections();
  684. }
  685. }
  686. $arr["fields"] = array_merge($arr["fields"], $a["fields"]);
  687. }
  688. if (!$this->hasFlag(AF_ONETOONE_INTEGRATE) && !$this->hasFlag(AF_NOLABEL)
  689. && !(count($a["fields"]) == 1 && $a["fields"][0]["name"] == $this->m_name)) {
  690. /* separator */
  691. $arr["fields"][] = array("html" => "-", "tabs" => $this->m_tabs, 'sections' => $this->getSections());
  692. }
  693. $fields = $arr['fields'];
  694. foreach ($fields as &$field) {
  695. $field['attribute'] = '';
  696. }
  697. }
  698. }
  699. }
  700. /**
  701. * Adds the attribute's view / hide HTML code to the view array.
  702. *
  703. * This method is called by the entity if it wants the data needed to create
  704. * a view form.
  705. *
  706. * This is a framework method, it should never be called directly.
  707. *
  708. * @param String $mode the mode ("view")
  709. * @param array $arr pointer to the view array
  710. * @param array $defaults pointer to the default values array
  711. */
  712. function addToViewArray($mode, &$arr, &$defaults)
  713. {
  714. if ($this->hasFlag(AF_HIDE_VIEW))
  715. return;
  716. /* we first check if there is no display override method, if there
  717. * is this method has the same behaviour as the atkAttribute's method
  718. */
  719. if (method_exists($this->m_ownerInstance, $this->m_name . "_display") || $this->display($defaults, 'view') !== NULL) {
  720. atkAttribute::addToViewArray($mode, $arr, $defaults);
  721. }
  722. /* how we handle 1:1 relations normally */
  723. else {
  724. if (!$this->createDestination())
  725. return;
  726. $record = $defaults[$this->fieldName()];
  727. $a = $this->m_destInstance->viewArray($mode, $record, false);
  728. /* editable fields, if AF_NOLABEL is specified or if there is just 1 field with the
  729. * same name as the relation we don't display a label
  730. * TODO FIXME
  731. */
  732. if (!is_array($arr['fields']))
  733. $arr['fields'] = array();
  734. if (!$this->hasFlag(AF_ONETOONE_INTEGRATE) && !$this->hasFlag(AF_NOLABEL)
  735. && !(count($a["fields"]) == 1 && $a["fields"][0]["name"] == $this->m_name)) {
  736. /* separator and name */
  737. if ($arr['fields'][count($arr['fields']) - 1]['html'] !== '-')
  738. $arr["fields"][] = array("html" => "-", "tabs" => $this->m_tabs, 'sections' => $this->getSections());
  739. $arr["fields"][] = array("line" => "<b>" . atktext($this->m_name, $this->m_ownerInstance->m_module, $this->m_ownerInstance->m_type) . "</b>",
  740. "tabs" => $this->m_tabs, 'sections' => $this->getSections());
  741. }
  742. if (is_array($a["fields"])) {
  743. if (!$this->hasFlag(AF_ONETOONE_INTEGRATE) || $this->hasFlag(AF_ONETOONE_RESPECT_TABS)) {
  744. foreach (array_keys($a['fields']) as $key) {
  745. $a['fields'][$key]['tabs'] = $this->m_tabs;
  746. $a['fields'][$key]['sections'] = $this->getSections();
  747. }
  748. }
  749. $arr["fields"] = array_merge($arr["fields"], $a["fields"]);
  750. }
  751. if (!$this->hasFlag(AF_ONETOONE_INTEGRATE) && !$this->hasFlag(AF_NOLABEL)
  752. && !(count($a["fields"]) == 1 && $a["fields"][0]["name"] == $this->m_name)) {
  753. /* separator */
  754. $arr["fields"][] = array("html" => "-", "tabs" => $this->m_tabs, 'sections' => $this->getSections());
  755. }
  756. }
  757. }
  758. /**
  759. * Check if a record has an empty value for this attribute.
  760. * @param array $record The record that holds this attribute's value.
  761. * @todo This method is not currently implemented properly and returns
  762. * false in all cases.
  763. * @return boolean
  764. */
  765. function isEmpty($record)
  766. {
  767. return false;
  768. }
  769. /**
  770. * Checks if a value is valid.
  771. *
  772. * For the Adapto_Relation_OneToOne, this method delegates the actual
  773. * validation of values to the destination entity.
  774. *
  775. * @param array $record The record that holds the value for this
  776. * attribute. If an error occurs, the error will
  777. * be stored in the 'atkerror' field of the record.
  778. * @param String $mode The mode for which should be validated ("add" or
  779. * "update")
  780. */
  781. function validate(&$record, $mode)
  782. {
  783. // zitten AF_ONETOONE_ERROR en AF_OBLIGATORY elkaar soms in de weg
  784. if ($this->hasFlag(AF_ONETOONE_ERROR) && ($mode != "add" || !$this->hasFlag(AF_HIDE_ADD)) && $this->createDestination()) {
  785. $this->m_destInstance->validate($record[$this->fieldName()], $mode, array($this->m_refKey));
  786. // only add 'atkerror' record when 1:1 relation contains error
  787. if (!isset($record[$this->fieldName()]["atkerror"])) {
  788. return;
  789. }
  790. foreach ($record[$this->fieldName()]["atkerror"] as $error) {
  791. $error['tab'] = $this->hasFlag(AF_ONETOONE_RESPECT_TABS) ? $this->m_tabs[0] : $error['tab'];
  792. $record["atkerror"][] = $error;
  793. }
  794. }
  795. }
  796. /**
  797. * Get list of additional tabs.
  798. *
  799. * Attributes can add new tabs to tabbed screens. This method will be
  800. * called to retrieve the tabs. When AF_ONETOONE_INTEGRATE is set, the
  801. * Adapto_Relation_OneToOne adds tabs from the destination entity to the tab
  802. * screen, so the attributes are seamlessly integrated but still on their
  803. * own tabs.
  804. *
  805. * @param String $action The action for which additional tabs should be
  806. * loaded.
  807. * @return array The list of tabs to add to the screen.
  808. */
  809. function getAdditionalTabs($action)
  810. {
  811. if ($this->hasFlag(AF_ONETOONE_INTEGRATE) && $this->createDestination()) {
  812. $detailtabs = $this->m_destInstance->getTabs($action);
  813. if (count($detailtabs) == 1 && $detailtabs[0] == "default") {
  814. // All elements in the relation are on the default tab. That means we should
  815. // inherit the tab from the onetoonerelation itself.
  816. return parent::getAdditionalTabs($action);
  817. }
  818. return $detailtabs;
  819. }
  820. return parent::getAdditionalTabs($action);
  821. }
  822. /**
  823. * Check if the attribute wants to be shown on a certain tab.
  824. *
  825. * @param String $tab The name of the tab to check.
  826. * @return boolean
  827. */
  828. function showOnTab($tab)
  829. {
  830. if ($this->hasFlag(AF_ONETOONE_INTEGRATE) && $this->createDestination()) {
  831. foreach (array_keys($this->m_destInstance->m_attribList) as $attribname) {
  832. $p_attrib = &$this->m_destInstance->m_attribList[$attribname];
  833. if ($p_attrib->showOnTab($tab))
  834. return true;
  835. // If we have one match, we can return true.
  836. }
  837. // None of the destionation attributes wants to be displayed on the tab.
  838. // If the entire onetoone itself is on that tab however, we should put all attribs on
  839. // this tab.
  840. return parent::showOnTab($tab);
  841. }
  842. return parent::showOnTab($tab);
  843. }
  844. /**
  845. * Adds the attribute / field to the list header. This includes the column name and search field.
  846. *
  847. * Framework method. It should not be necessary to call this method directly.
  848. *
  849. * @param String $action the action that is being performed on the entity
  850. * @param array $arr reference to the the recordlist array
  851. * @param String $fieldprefix the fieldprefix
  852. * @param int $flags the recordlist flags
  853. * @param array $atksearch the current ATK search list (if not empty)
  854. * @param String $atkorderby the current ATK orderby string (if not empty)
  855. * @see atkEntity::listArray
  856. */
  857. function addToListArrayHeader($action, &$arr, $fieldprefix, $flags, $atksearch, $columnConfig, atkDataGrid $grid = null, $column = '*')
  858. {
  859. if ($this->hasFlag(AF_HIDE_LIST) || !$this->createDestination()) {
  860. return;
  861. }
  862. if ((!$this->hasFlag(AF_ONETOONE_INTEGRATE) && $column == '*') || $column == null) {
  863. // regular behaviour.
  864. parent::addToListArrayHeader($action, $arr, $fieldprefix, $flags, $atksearch, $columnConfig, $grid, $column);
  865. return;
  866. } else if (!$this->hasFlag(AF_ONETOONE_INTEGRATE) || ($column != '*' && $this->getDestination()->getAttribute($column) == null)) {
  867. throw new Exception("Invalid list column {$column} for Adapto_Relation_OneToOne " . $this->getOwnerInstance()->atkEntityType() . '::'
  868. . $this->fieldName());
  869. }
  870. // integrated version, don't add ourselves, but add all columns from the destination.
  871. $prefix = $fieldprefix . $this->fieldName() . "_AE_";
  872. foreach (array_keys($this->m_destInstance->m_attribList) as $attribname) {
  873. if ($column != '*' && $column != $attribname) {
  874. continue;
  875. }
  876. $p_attrib = &$this->m_destInstance->getAttribute($attribname);
  877. $p_attrib->addToListArrayHeader($action, $arr, $prefix, $flags, $atksearch[$this->fieldName()], $columnConfig, $grid, null);
  878. }
  879. }
  880. /**
  881. * Adds the attribute / field to the list row. And if the row is totalisable also to the total.
  882. *
  883. * Framework method. It should not be necessary to call this method directly.
  884. *
  885. * @param String $action the action that is being performed on the entity
  886. * @param array $arr reference to the the recordlist array
  887. * @param int $nr the current row number
  888. * @param String $fieldprefix the fieldprefix
  889. * @param int $flags the recordlist flags
  890. * @see atkEntity::listArray
  891. */
  892. function addToListArrayRow($action, &$arr, $nr, $fieldprefix, $flags, $edit = false, atkDataGrid $grid = null, $column = '*')
  893. {
  894. if ($this->hasFlag(AF_HIDE_LIST) || !$this->createDestination()) {
  895. return;
  896. }
  897. if ((!$this->hasFlag(AF_ONETOONE_INTEGRATE) && $column == '*') || $column == null) {
  898. parent::addToListArrayRow($action, $arr, $nr, $fieldprefix, $flags, $edit, $grid, $column);
  899. return;
  900. } else if (!$this->hasFlag(AF_ONETOONE_INTEGRATE) || ($column != '*' && $this->getDestination()->getAttribute($column) == null)) {
  901. throw new Exception("Invalid list column {$column} for Adapto_Relation_OneToOne " . $this->getOwnerInstance()->atkEntityType() . '::'
  902. . $this->fieldName());
  903. }
  904. // integrated version, don't add ourselves, but add all columns from the destination
  905. // small trick, the destination record is in a subarray. The destination
  906. // addToListArrayRow will not expect this though, so we have to modify the
  907. // record a bit before passing it to the detail columns.
  908. $oldrecord = $arr["rows"][$nr]["record"];
  909. $arr["rows"][$nr]["record"] = $arr["rows"][$nr]["record"][$this->fieldName()];
  910. $prefix = $fieldprefix . $this->fieldName() . "_AE_";
  911. foreach (array_keys($this->m_destInstance->m_attribList) as $attribname) {
  912. if ($column != '*' && $column != $attribname) {
  913. continue;
  914. }
  915. $p_attrib = &$this->m_destInstance->getAttribute($attribname);
  916. $p_attrib->addToListArrayRow($action, $arr, $nr, $prefix, $flags, $edit, $grid, null);
  917. }
  918. $arr["rows"][$nr]["record"] = $oldrecord;
  919. }
  920. /**
  921. * Creates a searchcondition for the field,
  922. * was once part of searchCondition, however,
  923. * searchcondition() also immediately adds the search condition.
  924. *
  925. * @param atkQuery $query The query object where the search condition should be placed on
  926. * @param String $table The name of the table in which this attribute
  927. * is stored
  928. * @param mixed $value The value the user has entered in the searchbox
  929. * @param String $searchmode The searchmode to use. This can be any one
  930. * of the supported modes, as returned by this
  931. * attribute's getSearchModes() method.
  932. * @return String The searchcondition to use.
  933. */
  934. function getSearchCondition(&$query, $table, $value, $searchmode)
  935. {
  936. if ($this->createDestination() && is_array($value)) {
  937. // we are a relation, so instead of hooking ourselves into the
  938. // query, hook the attributes in the destination entity onto the query
  939. foreach ($value as $key => $val) {
  940. // if we aren't searching for anything in this field, there is no need
  941. // to look any further:
  942. if ($val === "" || $val === null)
  943. continue;
  944. $p_attrib = &$this->m_destInstance->m_attribList[$key];
  945. if (is_object($p_attrib)) {
  946. if ($this->m_refKey && $this->createDestination()) {
  947. // master mode
  948. $new_table = &$this->fieldName();
  949. } else {
  950. // slave mode
  951. $new_table = &$this->m_destInstance->m_table;
  952. // we need to left join the destination table into the query
  953. // (don't worry ATK won't add it when it's already there)
  954. $query->addJoin($new_table, $new_table, ($this->getJoinCondition($query)), false);
  955. }
  956. $p_attrib->searchCondition($query, $new_table, $val, $this->getChildSearchMode($searchmode, $p_attrib->formName()));
  957. } else {
  958. // attribute not found in destination, so it should
  959. // be in the owner (this is the case when extra fields
  960. // are in the relation
  961. $p_attrib = &$this->m_ownerInstance->m_attribList[$key];
  962. if (is_object($p_attrib)) {
  963. $p_attrib->searchCondition($query, $p_attrib->getTable($key), $val, $this->getChildSearchMode($searchmode, $p_attrib->formName()));
  964. } else
  965. Adapto_Util_Debugger::debug("Field $key was not found in this relation (this is very weird)");
  966. }
  967. }
  968. } else {
  969. // we were passed a value that is not an array, so appearantly the function calling us
  970. // does not know we are a relation, not just another attrib
  971. // so we assume that it is looking for something in the descriptor def of the destination
  972. if ($this->createDestination()) {
  973. $descfields = $this->m_destInstance->descriptorFields();
  974. foreach ($descfields as $key) {
  975. $p_attrib = &$this->m_destInstance->m_attribList[$key];
  976. if (is_object($p_attrib)) {
  977. if ($this->m_refKey && $this->createDestination()) {
  978. // master mode
  979. $new_table = &$this->fieldName();
  980. } else {
  981. // slave mode
  982. $new_table = &$this->m_destInstance->m_table;
  983. // we need to left join the destination table into the query
  984. // (don't worry ATK won't add it when it's already there)
  985. $query->addJoin($new_table, $new_table, ($this->getJoinCondition()), false);
  986. }
  987. $p_attrib->searchCondition($query, $new_table, $value, $searchmode);
  988. }
  989. }
  990. }
  991. }
  992. }
  993. /**
  994. * Returns the condition which can be used when calling atkQuery's addJoin() method
  995. * Joins the relation's owner with the destination
  996. *
  997. * @param atkQuery $query The query object
  998. * @param string $tablename The name of the table
  999. * @param string $fieldalias The field alias
  1000. * @return string condition the condition that can be pasted into the query
  1001. */
  1002. function getJoinCondition(&$query, $tablename = "", $fieldalias = "")
  1003. {
  1004. $condition = $this->m_ownerInstance->m_table . "." . $this->fieldName();
  1005. $condition .= "=";
  1006. $condition .= $this->m_destInstance->m_table . "." . $this->m_destInstance->primaryKeyField();
  1007. return $condition;
  1008. }
  1009. /**
  1010. * Overridden method; in the integrated version, we should let the destination
  1011. * attributes hook themselves into the fieldlist instead of hooking the relation
  1012. * in it.
  1013. * For original documentation for this method, please see the atkAttribute class
  1014. *
  1015. * @param array $fields The array containing fields to use in the
  1016. * extended search
  1017. * @param atkEntity $entity The entity where the field is in
  1018. * @param array $record A record containing default values to put
  1019. * into the search fields.
  1020. * @param array $fieldprefix search …

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