PageRenderTime 49ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/fof/table/relations.php

https://github.com/J2MTecnologia/joomla-3.x
PHP | 942 lines | 508 code | 131 blank | 303 comment | 69 complexity | 9df39f8c2734059138c8fa8761c20577 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. /**
  3. * @package FrameworkOnFramework
  4. * @subpackage table
  5. * @copyright Copyright (C) 2010 - 2014 Akeeba Ltd. All rights reserved.
  6. * @license GNU General Public License version 2 or later; see LICENSE.txt
  7. */
  8. // Protect from unauthorized access
  9. defined('FOF_INCLUDED') or die;
  10. class FOFTableRelations
  11. {
  12. /**
  13. * Holds all known relation definitions
  14. *
  15. * @var array
  16. */
  17. private $relations = array(
  18. 'child' => array(),
  19. 'parent' => array(),
  20. 'children' => array(),
  21. 'multiple' => array(),
  22. );
  23. /**
  24. * Holds the default relations' keys
  25. *
  26. * @var array
  27. */
  28. private $defaultRelation = array(
  29. 'child' => null,
  30. 'parent' => null,
  31. 'children' => null,
  32. 'multiple' => null,
  33. );
  34. /**
  35. * The table these relations are attached to
  36. *
  37. * @var FOFTable
  38. */
  39. private $table = null;
  40. /**
  41. * The name of the component used by our attached table
  42. *
  43. * @var string
  44. */
  45. private $componentName = 'joomla';
  46. /**
  47. * The type (table name without prefix and component name) of our attached table
  48. *
  49. * @var string
  50. */
  51. private $tableType = '';
  52. /**
  53. * Create a relations object based on the provided FOFTable instance
  54. *
  55. * @param FOFTable $table The table instance used to initialise the relations
  56. */
  57. public function __construct(FOFTable $table)
  58. {
  59. // Store the table
  60. $this->table = $table;
  61. // Get the table's type from its name
  62. $tableName = $table->getTableName();
  63. $tableName = str_replace('#__', '', $tableName);
  64. $type = explode("_", $tableName);
  65. if (count($type) == 1)
  66. {
  67. $this->tableType = array_pop($type);
  68. }
  69. else
  70. {
  71. $this->componentName = array_shift($type);
  72. $this->tableType = array_pop($type);
  73. }
  74. $this->tableType = FOFInflector::singularize($this->tableType);
  75. $tableKey = $table->getKeyName();
  76. unset($type);
  77. // Scan all table keys and look for foo_bar_id fields. These fields are used to populate parent relations.
  78. foreach ($table->getKnownFields() as $field)
  79. {
  80. // Skip the table key name
  81. if ($field == $tableKey)
  82. {
  83. continue;
  84. }
  85. if (substr($field, -3) != '_id')
  86. {
  87. continue;
  88. }
  89. $parts = explode('_', $field);
  90. // If the component type of the field is not set assume 'joomla'
  91. if (count($parts) == 2)
  92. {
  93. array_unshift($parts, 'joomla');
  94. }
  95. // Sanity check
  96. if (count($parts) != 3)
  97. {
  98. continue;
  99. }
  100. // Make sure we skip any references back to ourselves (should be redundant, due to key field check above)
  101. if ($parts[1] == $this->tableType)
  102. {
  103. continue;
  104. }
  105. // Default item name: the name of the table, singular
  106. $itemName = FOFInflector::singularize($parts[1]);
  107. // Prefix the item name with the component name if we refer to a different component
  108. if ($parts[0] != $this->componentName)
  109. {
  110. $itemName = $parts[0] . '_' . $itemName;
  111. }
  112. // Figure out the table class
  113. $tableClass = ucfirst($parts[0]) . 'Table' . ucfirst($parts[1]);
  114. $default = empty($this->relations['parent']);
  115. $this->addParentRelation($itemName, $tableClass, $field, $field, $default);
  116. }
  117. // Get the relations from the configuration provider
  118. $key = $table->getConfigProviderKey() . '.relations';
  119. $configRelations = $table->getConfigProvider()->get($key, array());
  120. if (!empty($configRelations))
  121. {
  122. foreach ($configRelations as $relation)
  123. {
  124. if (empty($relation['type']))
  125. {
  126. continue;
  127. }
  128. if (isset($relation['pivotTable']))
  129. {
  130. $this->addMultipleRelation($relation['itemName'], $relation['tableClass'],
  131. $relation['localKey'], $relation['ourPivotKey'], $relation['theirPivotKey'],
  132. $relation['remoteKey'], $relation['pivotTable'], $relation['default']);
  133. }
  134. else
  135. {
  136. $method = 'add' . ucfirst($relation['type']). 'Relation';
  137. if (!method_exists($this, $method))
  138. {
  139. continue;
  140. }
  141. $this->$method($relation['itemName'], $relation['tableClass'],
  142. $relation['localKey'], $relation['remoteKey'], $relation['default']);
  143. }
  144. }
  145. }
  146. }
  147. /**
  148. * Add a 1:1 forward (child) relation. This adds relations for the getChild() method.
  149. *
  150. * In other words: does a table HAVE ONE child
  151. *
  152. * Parent and child relations works the same way. We have them separated as it makes more sense for us humans to
  153. * read code like $item->getParent() and $item->getChild() than $item->getRelatedObject('someRandomKeyName')
  154. *
  155. * @param string $itemName is how it will be known locally to the getRelatedItem method (singular)
  156. * @param string $tableClass if skipped it is defined automatically as ComponentnameTableItemname
  157. * @param string $localKey is the column containing our side of the FK relation, default: our primary key
  158. * @param string $remoteKey is the remote table's FK column, default: componentname_itemname_id
  159. * @param boolean $default add as the default child relation?
  160. *
  161. * @return void
  162. */
  163. public function addChildRelation($itemName, $tableClass = null, $localKey = null, $remoteKey = null, $default = true)
  164. {
  165. $itemName = $this->normaliseItemName($itemName, false);
  166. if (empty($localKey))
  167. {
  168. $localKey = $this->table->getKeyName();
  169. }
  170. $this->addBespokeSimpleRelation('child', $itemName, $tableClass, $localKey, $remoteKey, $default);
  171. }
  172. /**
  173. * Defining an inverse 1:1 (parent) relation. You must specify at least the $tableClass or the $localKey.
  174. * This adds relations for the getParent() method.
  175. *
  176. * In other words: does a table BELONG TO ONE parent
  177. *
  178. * Parent and child relations works the same way. We have them separated as it makes more sense for us humans to
  179. * read code like $item->getParent() and $item->getChild() than $item->getRelatedObject('someRandomKeyName')
  180. *
  181. * @param string $itemName is how it will be known locally to the getRelatedItem method (singular)
  182. * @param string $tableClass if skipped it is defined automatically as ComponentnameTableItemname
  183. * @param string $localKey is the column containing our side of the FK relation, default: componentname_itemname_id
  184. * @param string $remoteKey is the remote table's FK column, default: componentname_itemname_id
  185. * @param boolean $default Is this the default parent relationship?
  186. *
  187. * @return void
  188. */
  189. public function addParentRelation($itemName, $tableClass = null, $localKey = null, $remoteKey = null, $default = true)
  190. {
  191. $itemName = $this->normaliseItemName($itemName, false);
  192. $this->addBespokeSimpleRelation('parent', $itemName, $tableClass, $localKey, $remoteKey, $default);
  193. }
  194. /**
  195. * Defining a forward 1:∞ (children) relation. This adds relations to the getChildren() method.
  196. *
  197. * In other words: does a table HAVE MANY children?
  198. *
  199. * The children relation works very much the same as the parent and child relation. The difference is that the
  200. * parent and child relations return a single table object, whereas the children relation returns an iterator to
  201. * many objects.
  202. *
  203. * @param string $itemName is how it will be known locally to the getRelatedItems method (plural)
  204. * @param string $tableClass if skipped it is defined automatically as ComponentnameTableItemname
  205. * @param string $localKey is the column containing our side of the FK relation, default: our primary key
  206. * @param string $remoteKey is the remote table's FK column, default: componentname_itemname_id
  207. * @param boolean $default is this the default children relationship?
  208. *
  209. * @return void
  210. */
  211. public function addChildrenRelation($itemName, $tableClass = null, $localKey = null, $remoteKey = null, $default = true)
  212. {
  213. $itemName = $this->normaliseItemName($itemName, true);
  214. if (empty($localKey))
  215. {
  216. $localKey = $this->table->getKeyName();
  217. }
  218. $this->addBespokeSimpleRelation('children', $itemName, $tableClass, $localKey, $remoteKey, $default);
  219. }
  220. /**
  221. * Defining a ∞:∞ (multiple) relation. This adds relations to the getMultiple() method.
  222. *
  223. * In other words: is a table RELATED TO MANY other records?
  224. *
  225. * @param string $itemName is how it will be known locally to the getRelatedItems method (plural)
  226. * @param string $tableClass if skipped it is defined automatically as ComponentnameTableItemname
  227. * @param string $localKey is the column containing our side of the FK relation, default: our primary key field name
  228. * @param string $ourPivotKey is the column containing our side of the FK relation in the pivot table, default: $localKey
  229. * @param string $theirPivotKey is the column containing the other table's side of the FK relation in the pivot table, default $remoteKey
  230. * @param string $remoteKey is the remote table's FK column, default: componentname_itemname_id
  231. * @param string $glueTable is the name of the glue (pivot) table, default: #__componentname_thisclassname_itemname with plural items (e.g. #__foobar_users_roles)
  232. * @param boolean $default is this the default multiple relation?
  233. */
  234. public function addMultipleRelation($itemName, $tableClass = null, $localKey = null, $ourPivotKey = null, $theirPivotKey = null, $remoteKey = null, $glueTable = null, $default = true)
  235. {
  236. $itemName = $this->normaliseItemName($itemName, true);
  237. if (empty($localKey))
  238. {
  239. $localKey = $this->table->getKeyName();
  240. }
  241. $this->addBespokePivotRelation('multiple', $itemName, $tableClass, $localKey, $remoteKey, $ourPivotKey, $theirPivotKey, $glueTable, $default);
  242. }
  243. /**
  244. * Removes a previously defined relation by name. You can optionally specify the relation type.
  245. *
  246. * @param string $itemName The name of the relation to remove
  247. * @param string $type [optional] The relation type (child, parent, children, ...)
  248. *
  249. * @return void
  250. */
  251. public function removeRelation($itemName, $type = null)
  252. {
  253. $types = array_keys($this->relations);
  254. if (in_array($type, $types))
  255. {
  256. $types = array($type);
  257. }
  258. foreach ($types as $type)
  259. {
  260. foreach ($this->relations[$type] as $relations)
  261. {
  262. if (array_key_exists($itemName, $relations))
  263. {
  264. unset ($this->relations[$type][$itemName]);
  265. return;
  266. }
  267. }
  268. }
  269. }
  270. /**
  271. * Removes all existing relations
  272. *
  273. * @param string $type The type or relations to remove, omit to remove all relation types
  274. *
  275. * @return void
  276. */
  277. public function clearRelations($type = null)
  278. {
  279. $types = array_keys($this->relations);
  280. if (in_array($type, $types))
  281. {
  282. $types = array($type);
  283. }
  284. foreach ($types as $type)
  285. {
  286. $this->relations[$type] = array();
  287. }
  288. }
  289. /**
  290. * Does the named relation exist? You can optionally specify the type.
  291. *
  292. * @param string $itemName The name of the relation to check
  293. * @param string $type [optional] The relation type (child, parent, children, ...)
  294. *
  295. * @return boolean
  296. */
  297. public function hasRelation($itemName, $type = null)
  298. {
  299. $types = array_keys($this->relations);
  300. if (in_array($type, $types))
  301. {
  302. $types = array($type);
  303. }
  304. foreach ($types as $type)
  305. {
  306. foreach ($this->relations[$type] as $relations)
  307. {
  308. if (array_key_exists($itemName, $relations))
  309. {
  310. return true;
  311. }
  312. }
  313. }
  314. return false;
  315. }
  316. /**
  317. * Get the definition of a relation
  318. *
  319. * @param string $itemName The name of the relation to check
  320. * @param string $type [optional] The relation type (child, parent, children, ...)
  321. *
  322. * @return array
  323. *
  324. * @throws RuntimeException When the relation is not found
  325. */
  326. public function getRelation($itemName, $type)
  327. {
  328. $types = array_keys($this->relations);
  329. if (in_array($type, $types))
  330. {
  331. $types = array($type);
  332. }
  333. foreach ($types as $type)
  334. {
  335. foreach ($this->relations[$type] as $relations)
  336. {
  337. if (array_key_exists($itemName, $relations))
  338. {
  339. $temp = $relations[$itemName];
  340. $temp['type'] = $type;
  341. return $temp;
  342. }
  343. }
  344. }
  345. throw new RuntimeException("Relation $itemName not found in table {$this->tableType}", 500);
  346. }
  347. /**
  348. * Gets the item referenced by a named relation. You can optionally specify the type. Only single item relation
  349. * types will be searched.
  350. *
  351. * @param string $itemName The name of the relation to use
  352. * @param string $type [optional] The relation type (child, parent)
  353. *
  354. * @return FOFTable
  355. *
  356. * @throws RuntimeException If the named relation doesn't exist or isn't supposed to return single items
  357. */
  358. public function getRelatedItem($itemName, $type = null)
  359. {
  360. if (empty($type))
  361. {
  362. $relation = $this->getRelation($itemName, $type);
  363. $type = $relation['type'];
  364. }
  365. switch ($type)
  366. {
  367. case 'parent':
  368. return $this->getParent($itemName);
  369. break;
  370. case 'child':
  371. return $this->getChild($itemName);
  372. break;
  373. default:
  374. throw new RuntimeException("Invalid relation type $type for returning a single related item", 500);
  375. break;
  376. }
  377. }
  378. /**
  379. * Gets the iterator for the items referenced by a named relation. You can optionally specify the type. Only
  380. * multiple item relation types will be searched.
  381. *
  382. * @param string $itemName The name of the relation to use
  383. * @param string $type [optional] The relation type (children, multiple)
  384. *
  385. * @return FOFDatabaseIterator
  386. *
  387. * @throws RuntimeException If the named relation doesn't exist or isn't supposed to return single items
  388. */
  389. public function getRelatedItems($itemName, $type = null)
  390. {
  391. if (empty($type))
  392. {
  393. $relation = $this->getRelation($itemName, $type);
  394. $type = $relation['type'];
  395. }
  396. switch ($type)
  397. {
  398. case 'children':
  399. return $this->getChildren($itemName);
  400. break;
  401. case 'multiple':
  402. return $this->getMultiple($itemName);
  403. break;
  404. case 'siblings':
  405. return $this->getSiblings($itemName);
  406. break;
  407. default:
  408. throw new RuntimeException("Invalid relation type $type for returning a collection of related items", 500);
  409. break;
  410. }
  411. }
  412. /**
  413. * Gets a parent item
  414. *
  415. * @param string $itemName [optional] The name of the relation to use, skip to use the default parent relation
  416. *
  417. * @return FOFTable
  418. *
  419. * @throws RuntimeException When the relation is not found
  420. */
  421. public function getParent($itemName = null)
  422. {
  423. if (empty($itemName))
  424. {
  425. $itemName = $this->defaultRelation['parent'];
  426. }
  427. if (empty($itemName))
  428. {
  429. throw new RuntimeException(sprintf('Default parent relation for %s not found', $this->table->getTableName()), 500);
  430. }
  431. if (!isset($this->relations['parent'][$itemName]))
  432. {
  433. throw new RuntimeException(sprintf('Parent relation %s for %s not found', $itemName, $this->table->getTableName()), 500);
  434. }
  435. return $this->getTableFromRelation($this->relations['parent'][$itemName]);
  436. }
  437. /**
  438. * Gets a child item
  439. *
  440. * @param string $itemName [optional] The name of the relation to use, skip to use the default child relation
  441. *
  442. * @return FOFTable
  443. *
  444. * @throws RuntimeException When the relation is not found
  445. */
  446. public function getChild($itemName = null)
  447. {
  448. if (empty($itemName))
  449. {
  450. $itemName = $this->defaultRelation['child'];
  451. }
  452. if (empty($itemName))
  453. {
  454. throw new RuntimeException(sprintf('Default child relation for %s not found', $this->table->getTableName()), 500);
  455. }
  456. if (!isset($this->relations['child'][$itemName]))
  457. {
  458. throw new RuntimeException(sprintf('Child relation %s for %s not found', $itemName, $this->table->getTableName()), 500);
  459. }
  460. return $this->getTableFromRelation($this->relations['child'][$itemName]);
  461. }
  462. /**
  463. * Gets an iterator for the children items
  464. *
  465. * @param string $itemName [optional] The name of the relation to use, skip to use the default children relation
  466. *
  467. * @return FOFDatabaseIterator
  468. *
  469. * @throws RuntimeException When the relation is not found
  470. */
  471. public function getChildren($itemName = null)
  472. {
  473. if (empty($itemName))
  474. {
  475. $itemName = $this->defaultRelation['children'];
  476. }
  477. if (empty($itemName))
  478. {
  479. throw new RuntimeException(sprintf('Default children relation for %s not found', $this->table->getTableName()), 500);
  480. }
  481. if (!isset($this->relations['children'][$itemName]))
  482. {
  483. throw new RuntimeException(sprintf('Children relation %s for %s not found', $itemName, $this->table->getTableName()), 500);
  484. }
  485. return $this->getIteratorFromRelation($this->relations['children'][$itemName]);
  486. }
  487. /**
  488. * Gets an iterator for the sibling items. This relation is inferred from the parent relation. It returns all
  489. * elements on the same table which have the same parent.
  490. *
  491. * @param string $itemName [optional] The name of the relation to use, skip to use the default children relation
  492. *
  493. * @return FOFDatabaseIterator
  494. *
  495. * @throws RuntimeException When the relation is not found
  496. */
  497. public function getSiblings($itemName = null)
  498. {
  499. if (empty($itemName))
  500. {
  501. $itemName = $this->defaultRelation['parent'];
  502. }
  503. if (empty($itemName))
  504. {
  505. throw new RuntimeException(sprintf('Default siblings relation for %s not found', $this->table->getTableName()), 500);
  506. }
  507. if (!isset($this->relations['parent'][$itemName]))
  508. {
  509. throw new RuntimeException(sprintf('Sibling relation %s for %s not found', $itemName, $this->table->getTableName()), 500);
  510. }
  511. // Get my table class
  512. $tableName = $this->table->getTableName();
  513. $tableName = str_replace('#__', '', $tableName);
  514. $tableNameParts = explode('_', $tableName, 2);
  515. $tableClass = ucfirst($tableNameParts[0]) . 'Table' . ucfirst(FOFInflector::singularize($tableNameParts[1]));
  516. $parentRelation = $this->relations['parent'][$itemName];
  517. $relation = array(
  518. 'tableClass' => $tableClass,
  519. 'localKey' => $parentRelation['localKey'],
  520. 'remoteKey' => $parentRelation['localKey'],
  521. );
  522. return $this->getIteratorFromRelation($relation);
  523. }
  524. /**
  525. * Gets an iterator for the multiple items
  526. *
  527. * @param string $itemName [optional] The name of the relation to use, skip to use the default multiple relation
  528. *
  529. * @return FOFDatabaseIterator
  530. *
  531. * @throws RuntimeException When the relation is not found
  532. */
  533. public function getMultiple($itemName = null)
  534. {
  535. if (empty($itemName))
  536. {
  537. $itemName = $this->defaultRelation['multiple'];
  538. }
  539. if (empty($itemName))
  540. {
  541. throw new RuntimeException(sprintf('Default multiple relation for %s not found', $this->table->getTableName()), 500);
  542. }
  543. if (!isset($this->relations['multiple'][$itemName]))
  544. {
  545. throw new RuntimeException(sprintf('Multiple relation %s for %s not found', $itemName, $this->table->getTableName()), 500);
  546. }
  547. return $this->getIteratorFromRelation($this->relations['multiple'][$itemName]);
  548. }
  549. /**
  550. * Returns a FOFTable object based on a given relation
  551. *
  552. * @param array $relation Relation definition
  553. *
  554. * @return FOFTable
  555. *
  556. * @throws RuntimeException
  557. */
  558. private function getTableFromRelation($relation)
  559. {
  560. // Get a table object from the table class name
  561. $tableClass = $relation['tableClass'];
  562. $tableClassParts = FOFInflector::explode($tableClass);
  563. $table = FOFTable::getInstance($tableClassParts[2], ucfirst($tableClassParts[0]) . ucfirst($tableClassParts[1]));
  564. // Get the table name
  565. $tableName = $table->getTableName();
  566. // Get the remote and local key names
  567. $remoteKey = $relation['remoteKey'];
  568. $localKey = $relation['localKey'];
  569. // Get the local key's value
  570. $value = $this->table->$localKey;
  571. // This is required to prevent one relation from killing the db cursor used in a different relation...
  572. $oldDb = $this->table->getDbo();
  573. $oldDb->disconnect(); // YES, WE DO NEED TO DISCONNECT BEFORE WE CLONE THE DB OBJECT. ARGH!
  574. $db = clone $oldDb;
  575. $query = $db->getQuery(true)
  576. ->select('*')
  577. ->from($db->qn($tableName))
  578. ->where($db->qn($remoteKey) . ' = ' . $db->q($value));
  579. $db->setQuery($query, 0, 1);
  580. $data = $db->loadObject();
  581. if (!is_object($data))
  582. {
  583. throw new RuntimeException(sprintf('Cannot load item from relation against table %s column %s', $tableName, $remoteKey), 500);
  584. }
  585. $table->bind($data);
  586. return $table;
  587. }
  588. /**
  589. * Returns a FOFDatabaseIterator based on a given relation
  590. *
  591. * @param array $relation Relation definition
  592. *
  593. * @return FOFDatabaseIterator
  594. *
  595. * @throws RuntimeException
  596. */
  597. private function getIteratorFromRelation($relation)
  598. {
  599. // Get a table object from the table class name
  600. $tableClass = $relation['tableClass'];
  601. $tableClassParts = FOFInflector::explode($tableClass);
  602. $table = FOFTable::getInstance($tableClassParts[2], ucfirst($tableClassParts[0]) . ucfirst($tableClassParts[1]));
  603. // Get the table name
  604. $tableName = $table->getTableName();
  605. // Get the remote and local key names
  606. $remoteKey = $relation['remoteKey'];
  607. $localKey = $relation['localKey'];
  608. // Get the local key's value
  609. $value = $this->table->$localKey;
  610. // This is required to prevent one relation from killing the db cursor used in a different relation...
  611. $oldDb = $this->table->getDbo();
  612. $oldDb->disconnect(); // YES, WE DO NEED TO DISCONNECT BEFORE WE CLONE THE DB OBJECT. ARGH!
  613. $db = clone $oldDb;
  614. // Begin the query
  615. $query = $db->getQuery(true)
  616. ->select('*')
  617. ->from($db->qn($tableName));
  618. // Do we have a pivot table?
  619. $hasPivot = array_key_exists('pivotTable', $relation);
  620. // If we don't have pivot it's a straightforward query
  621. if (!$hasPivot)
  622. {
  623. $query
  624. ->where($db->qn($remoteKey) . ' = ' . $db->q($value));
  625. }
  626. // If we have a pivot table we have to do a subquery
  627. else
  628. {
  629. $subQuery = $db->getQuery(true)
  630. ->select($db->qn($relation['theirPivotKey']))
  631. ->from($db->qn($relation['pivotTable']))
  632. ->where($db->qn($relation['ourPivotKey']) . ' = ' . $db->q($value));
  633. $query->where($db->qn($remoteKey) . ' IN (' . $subQuery . ')');
  634. }
  635. $db->setQuery($query);
  636. $cursor = $db->execute();
  637. $iterator = FOFDatabaseIterator::getIterator($db->name, $cursor, null, $tableClass);
  638. return $iterator;
  639. }
  640. /**
  641. * Add any bespoke relation which doesn't involve a pivot table.
  642. *
  643. * @param string $relationType The type of the relationship (parent, child, children)
  644. * @param string $itemName is how it will be known locally to the getRelatedItems method
  645. * @param string $tableClass if skipped it is defined automatically as ComponentnameTableItemname
  646. * @param string $localKey is the column containing our side of the FK relation, default: componentname_itemname_id
  647. * @param string $remoteKey is the remote table's FK column, default: componentname_itemname_id
  648. * @param boolean $default is this the default children relationship?
  649. *
  650. * @return void
  651. */
  652. private function addBespokeSimpleRelation($relationType, $itemName, $tableClass, $localKey, $remoteKey, $default)
  653. {
  654. $ourPivotKey = null;
  655. $theirPivotKey = null;
  656. $pivotTable = null;
  657. $this->normaliseParameters(false, $itemName, $tableClass, $localKey, $remoteKey, $ourPivotKey, $theirPivotKey, $pivotTable);
  658. $this->relations[$relationType][$itemName] = array(
  659. 'tableClass' => $tableClass,
  660. 'localKey' => $localKey,
  661. 'remoteKey' => $remoteKey,
  662. );
  663. if ($default)
  664. {
  665. $this->defaultRelation[$relationType] = $itemName;
  666. }
  667. }
  668. /**
  669. * Add any bespoke relation which involves a pivot table.
  670. *
  671. * @param string $relationType The type of the relationship (multiple)
  672. * @param string $itemName is how it will be known locally to the getRelatedItems method
  673. * @param string $tableClass if skipped it is defined automatically as ComponentnameTableItemname
  674. * @param string $localKey is the column containing our side of the FK relation, default: componentname_itemname_id
  675. * @param string $remoteKey is the remote table's FK column, default: componentname_itemname_id
  676. * @param string $ourPivotKey is the column containing our side of the FK relation in the pivot table, default: $localKey
  677. * @param string $theirPivotKey is the column containing the other table's side of the FK relation in the pivot table, default $remoteKey
  678. * @param string $pivotTable is the name of the glue (pivot) table, default: #__componentname_thisclassname_itemname with plural items (e.g. #__foobar_users_roles)
  679. * @param boolean $default is this the default children relationship?
  680. *
  681. * @return void
  682. */
  683. private function addBespokePivotRelation($relationType, $itemName, $tableClass, $localKey, $remoteKey, $ourPivotKey, $theirPivotKey, $pivotTable, $default)
  684. {
  685. $this->normaliseParameters(true, $itemName, $tableClass, $localKey, $remoteKey, $ourPivotKey, $theirPivotKey, $pivotTable);
  686. $this->relations[$relationType][$itemName] = array(
  687. 'tableClass' => $tableClass,
  688. 'localKey' => $localKey,
  689. 'remoteKey' => $remoteKey,
  690. 'ourPivotKey' => $ourPivotKey,
  691. 'theirPivotKey' => $theirPivotKey,
  692. 'pivotTable' => $pivotTable,
  693. );
  694. if ($default)
  695. {
  696. $this->defaultRelation[$relationType] = $itemName;
  697. }
  698. }
  699. /**
  700. * Normalise the parameters of a relation, guessing missing values
  701. *
  702. * @param boolean $pivot Is this a many to many relation involving a pivot table?
  703. * @param string $itemName is how it will be known locally to the getRelatedItems method (plural)
  704. * @param string $tableClass if skipped it is defined automatically as ComponentnameTableItemname
  705. * @param string $localKey is the column containing our side of the FK relation, default: componentname_itemname_id
  706. * @param string $remoteKey is the remote table's FK column, default: componentname_itemname_id
  707. * @param string $ourPivotKey is the column containing our side of the FK relation in the pivot table, default: $localKey
  708. * @param string $theirPivotKey is the column containing the other table's side of the FK relation in the pivot table, default $remoteKey
  709. * @param string $pivotTable is the name of the glue (pivot) table, default: #__componentname_thisclassname_itemname with plural items (e.g. #__foobar_users_roles)
  710. *
  711. * @return void
  712. */
  713. private function normaliseParameters($pivot = false, &$itemName, &$tableClass, &$localKey, &$remoteKey, &$ourPivotKey, &$theirPivotKey, &$pivotTable)
  714. {
  715. // Get a default table class if none is provided
  716. if (empty($tableClass))
  717. {
  718. $tableClassParts = explode('_', $itemName, 3);
  719. if (count($tableClassParts) == 1)
  720. {
  721. array_unshift($tableClassParts, $this->componentName);
  722. }
  723. if ($tableClassParts[0] == 'joomla')
  724. {
  725. $tableClassParts[0] = 'J';
  726. }
  727. $tableClass = ucfirst($tableClassParts[0]) . 'Table' . ucfirst(FOFInflector::singularize($tableClassParts[1]));
  728. }
  729. // Make sure we have both a local and remote key
  730. if (empty($localKey) && empty($remoteKey))
  731. {
  732. $tableClassParts = FOFInflector::explode($tableClass);
  733. $localKey = $tableClassParts[0] . '_' . $tableClassParts[2] . '_id';
  734. $remoteKey = $localKey;
  735. }
  736. elseif (empty($localKey) && !empty($remoteKey))
  737. {
  738. $localKey = $remoteKey;
  739. }
  740. elseif (!empty($localKey) && empty($remoteKey))
  741. {
  742. $remoteKey = $localKey;
  743. }
  744. // If we don't have a pivot table nullify the relevant variables and return
  745. if (!$pivot)
  746. {
  747. $ourPivotKey = null;
  748. $theirPivotKey = null;
  749. $pivotTable = null;
  750. return;
  751. }
  752. if (empty($ourPivotKey))
  753. {
  754. $ourPivotKey = $localKey;
  755. }
  756. if (empty($theirPivotKey))
  757. {
  758. $theirPivotKey = $remoteKey;
  759. }
  760. if (empty($pivotTable))
  761. {
  762. $pivotTable = '#__' . strtolower($this->componentName) . '_' .
  763. strtolower(FOFInflector::pluralize($this->tableType)) . '_';
  764. $itemNameParts = explode('_', $itemName);
  765. $lastPart = array_pop($itemNameParts);
  766. $pivotTable .= strtolower($lastPart);
  767. }
  768. }
  769. /**
  770. * Normalises the format of a relation name
  771. *
  772. * @param string $itemName The raw relation name
  773. * @param boolean $pluralise Should I pluralise the name? If not, I will singularise it
  774. *
  775. * @return string The normalised relation key name
  776. */
  777. private function normaliseItemName($itemName, $pluralise = false)
  778. {
  779. // Explode the item name
  780. $itemNameParts = explode('_', $itemName);
  781. // If we have multiple parts the first part is considered to be the component name
  782. if (count($itemNameParts) > 1)
  783. {
  784. $prefix = array_shift($itemNameParts);
  785. }
  786. else
  787. {
  788. $prefix = null;
  789. }
  790. // If we still have multiple parts we need to pluralise/singularise the last part and join everything in
  791. // CamelCase format
  792. if (count($itemNameParts) > 1)
  793. {
  794. $name = array_pop($itemNameParts);
  795. $name = $pluralise ? FOFInflector::pluralize($name) : FOFInflector::singularize($name);
  796. $itemNameParts[] = $name;
  797. $itemName = FOFInflector::implode($itemNameParts);
  798. }
  799. // Otherwise we singularise/pluralise the remaining part
  800. else
  801. {
  802. $name = array_pop($itemNameParts);
  803. $itemName = $pluralise ? FOFInflector::pluralize($name) : FOFInflector::singularize($name);
  804. }
  805. if (!empty($prefix))
  806. {
  807. $itemName = $prefix . '_' . $itemName;
  808. }
  809. return $itemName;
  810. }
  811. }