]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - data/Relationships/M2MRelationship.php
Release 6.4.0beta3
[Github/sugarcrm.git] / data / Relationships / M2MRelationship.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38
39 require_once("data/Relationships/SugarRelationship.php");
40
41 /**
42  * Represents a many to many relationship that is table based.
43  */
44 class M2MRelationship extends SugarRelationship 
45 {
46     var $type = "many-to-many";
47
48     public function __construct($def)
49     {
50         $this->def = $def;
51         $this->name = $def['name'];
52
53         $lhsModule = $def['lhs_module'];
54         $this->lhsLinkDef = $this->getLinkedDefForModuleByRelationship($lhsModule);
55         $this->lhsLink = $this->lhsLinkDef['name'];
56
57         $rhsModule = $def['rhs_module'];
58         $this->rhsLinkDef = $this->getLinkedDefForModuleByRelationship($rhsModule);
59         $this->rhsLink = $this->rhsLinkDef['name'];
60
61         $this->self_referencing = $lhsModule == $rhsModule;
62     }
63
64     /**
65      * Find the link entry for a particular relationship and module.
66      *
67      * @param $module
68      * @return array|bool
69      */
70     public function getLinkedDefForModuleByRelationship($module)
71     {
72         $results = VardefManager::getLinkFieldForRelationship( $module, BeanFactory::getObjectName($module), $this->name);
73         //Only a single link was found
74         if( isset($results['name']) )
75         {
76             return $results;
77         }
78         //Multiple links with same relationship name
79         else if( is_array($results) )
80         {
81             $GLOBALS['log']->error("Warning: Multiple links found for relationship {$this->name} within module {$module}");
82             return $this->getMostAppropriateLinkedDefinition($results);
83         }
84         else
85         {
86             return FALSE;
87         }
88     }
89
90     /**
91      * Find the most 'appropriate' link entry for a relationship/module in which there are multiple link entries with the
92      * same relationship name.
93      *
94      * @param $links
95      * @return bool
96      */
97     protected function getMostAppropriateLinkedDefinition($links)
98     {
99         //First priority is to find a link name that matches the relationship name
100         foreach($links as $link)
101         {
102             if( isset($link['name']) && $link['name'] == $this->name )
103             {
104                 return $link;
105             }
106         }
107         //Next would be a relationship that has a side defined
108         foreach($links as $link)
109         {
110             if( isset($link['id_name']))
111             {
112                 return $link;
113             }
114         }
115         //Unable to find an appropriate link, guess and use the first one
116         $GLOBALS['log']->error("Unable to determine best appropriate link for relationship {$this->name}");
117         return $links[0];
118     }
119     /**
120      * @param  $lhs SugarBean left side bean to add to the relationship.
121      * @param  $rhs SugarBean right side bean to add to the relationship.
122      * @param  $additionalFields key=>value pairs of fields to save on the relationship
123      * @return boolean true if successful
124      */
125     public function add($lhs, $rhs, $additionalFields = array())
126     {
127         $lhsLinkName = $this->lhsLink;
128         $rhsLinkName = $this->rhsLink;
129
130         if (empty($lhs->$lhsLinkName) && !$lhs->load_relationship($lhsLinkName))
131         {
132             $lhsClass = get_class($lhs);
133             $GLOBALS['log']->fatal("could not load LHS $lhsLinkName in $lhsClass");
134             return false;
135         }
136         if (empty($rhs->$rhsLinkName) && !$rhs->load_relationship($rhsLinkName))
137         {
138             $rhsClass = get_class($rhs);
139             $GLOBALS['log']->fatal("could not load RHS $rhsLinkName in $rhsClass");
140             return false;
141         }
142
143         //Many to many has no additional logic, so just add a new row to the table and notify the beans.
144         $dataToInsert = $this->getRowToInsert($lhs, $rhs, $additionalFields);
145
146         $this->addRow($dataToInsert);
147
148         if ($this->self_referencing)
149             $this->addSelfReferencing($lhs, $rhs, $additionalFields);
150
151             if ($lhs->$lhsLinkName->beansAreLoaded())
152                 $lhs->$lhsLinkName->addBean($rhs);
153             if ($rhs->$rhsLinkName->beansAreLoaded())
154                 $rhs->$rhsLinkName->addBean($lhs);
155
156             $this->callAfterAdd($lhs, $rhs, $lhsLinkName);
157             $this->callAfterAdd($rhs, $lhs, $rhsLinkName);
158     }
159
160     protected function getRowToInsert($lhs, $rhs, $additionalFields = array())
161     {
162         $row = array(
163             "id" => create_guid(),
164             $this->def['join_key_lhs'] => $lhs->id,
165             $this->def['join_key_rhs'] => $rhs->id,
166             'date_modified' => TimeDate::getInstance()->nowDb(),
167             'deleted' => 0,
168         );
169
170
171         if (!empty($this->def['relationship_role_column']) && !empty($this->def['relationship_role_column_value']) && !$this->ignore_role_filter )
172         {
173             $row[$this->relationship_role_column] = $this->relationship_role_column_value;
174         }
175
176         if (!empty($this->def['fields']))
177         {
178             foreach($this->def['fields'] as $fieldDef)
179             {
180                 if (!empty($fieldDef['name']) && !isset($row[$fieldDef['name']]) && !empty($fieldDef['default']))
181                 {
182                     $row[$fieldDef['name']] = $fieldDef['default'];
183                 }
184             }
185         }
186         if (!empty($additionalFields))
187         {
188             $row = array_merge($row, $additionalFields);
189         }
190
191         return $row;
192     }
193
194     /**
195      * Adds the reversed version of this relationship to the table so that it can be accessed from either side equally
196      * @param $lhs
197      * @param $rhs
198      * @param array $additionalFields
199      * @return void
200      */
201     protected function addSelfReferencing($lhs, $rhs, $additionalFields = array())
202     {
203         if ($rhs->id != $lhs->id)
204         {
205             $dataToInsert = $this->getRowToInsert($rhs, $lhs, $additionalFields);
206             $this->addRow($dataToInsert);
207         }
208     }
209
210     public function remove($lhs, $rhs)
211     {
212         if(!($lhs instanceof SugarBean) || !($rhs instanceof SugarBean)) {
213             $GLOBALS['log']->fatal("LHS and RHS must be beans");
214             return false;
215         }
216         $lhsLinkName = $this->lhsLink;
217         $rhsLinkName = $this->rhsLink;
218
219         if (!($lhs instanceof SugarBean)) {
220             $GLOBALS['log']->fatal("LHS is not a SugarBean object");
221             return false;
222         }
223         if (!($rhs instanceof SugarBean)) {
224             $GLOBALS['log']->fatal("RHS is not a SugarBean object");
225             return false;
226         }
227         if (empty($lhs->$lhsLinkName) && !$lhs->load_relationship($lhsLinkName))
228         {
229             $GLOBALS['log']->fatal("could not load LHS $lhsLinkName");
230             return false;
231         }
232         if (empty($rhs->$rhsLinkName) && !$rhs->load_relationship($rhsLinkName))
233         {
234             $GLOBALS['log']->fatal("could not load RHS $rhsLinkName");
235             return false;
236         }
237
238         $dataToRemove = array(
239             $this->def['join_key_lhs'] => $lhs->id,
240             $this->def['join_key_rhs'] => $rhs->id
241         );
242
243         $this->removeRow($dataToRemove);
244
245         if ($this->self_referencing)
246             $this->removeSelfReferencing($lhs, $rhs);
247
248         if (empty($_SESSION['disable_workflow']) || $_SESSION['disable_workflow'] != "Yes")
249         {
250             if ($lhs->$lhsLinkName instanceof Link2)
251             {
252                 $lhs->$lhsLinkName->load();
253                 $this->callAfterDelete($lhs, $rhs, $lhsLinkName);
254             }
255
256             if ($rhs->$rhsLinkName instanceof Link2)
257             {
258                 $rhs->$rhsLinkName->load();
259                 $this->callAfterDelete($rhs, $lhs, $rhsLinkName);
260             }
261         }
262     }
263
264     /**
265      * Removes the reversed version of this relationship
266      * @param $lhs
267      * @param $rhs
268      * @param array $additionalFields
269      * @return void
270      */
271     protected function removeSelfReferencing($lhs, $rhs, $additionalFields = array())
272     {
273         if ($rhs->id != $lhs->id)
274         {
275             $dataToRemove = array(
276                 $this->def['join_key_lhs'] => $rhs->id,
277                 $this->def['join_key_rhs'] => $lhs->id
278             );
279             $this->removeRow($dataToRemove);
280         }
281     }
282
283     /**
284      * @param  $link Link2 loads the relationship for this link.
285      * @return void
286      */
287     public function load($link)
288     {
289         $db = DBManagerFactory::getInstance();
290         $query = $this->getQuery($link);
291         $result = $db->query($query);
292         $rows = Array();
293         $idField = $link->getSide() == REL_LHS ? $this->def['join_key_rhs'] : $this->def['join_key_lhs'];
294         while ($row = $db->fetchByAssoc($result))
295         {
296             if (empty($row['id']) && empty($row[$idField]))
297                 continue;
298             $id = empty($row['id']) ? $row[$idField] : $row['id'];
299             $rows[$id] = $row;
300         }
301         return array("rows" => $rows);
302     }
303
304     public function getQuery($link, $params = array())
305     {
306         if ($link->getSide() == REL_LHS) {
307             $knownKey = $this->def['join_key_lhs'];
308             $targetKey = $this->def['join_key_rhs'];
309         }
310         else
311         {
312             $knownKey = $this->def['join_key_rhs'];
313             $targetKey = $this->def['join_key_lhs'];
314         }
315         $rel_table = $this->getRelationshipTable();
316
317         $where = "$rel_table.$knownKey = '{$link->getFocus()->id}'" . $this->getRoleWhere();
318
319         if (empty($params['return_as_array'])) {
320             return "SELECT $targetKey id FROM $rel_table WHERE $where AND deleted=0";
321         }
322         else
323         {
324             return array(
325                 'select' => "SELECT $targetKey id",
326                 'from' => "FROM $rel_table",
327                 'where' => "WHERE $where AND $rel_table.deleted=0",
328             );
329         }
330     }
331
332     public function getJoin($link, $params = array(), $return_array = false)
333     {
334         $linkIsLHS = $link->getSide() == REL_LHS;
335         if ($linkIsLHS) {
336             $startingTable = (empty($params['left_join_table_alias']) ? $link->getFocus()->table_name : $params['left_join_table_alias']);
337         } else {
338             $startingTable = (empty($params['right_join_table_alias']) ? $link->getFocus()->table_name : $params['right_join_table_alias']);
339         }
340
341         $startingKey = $linkIsLHS ? $this->def['lhs_key'] : $this->def['rhs_key'];
342         $startingJoinKey = $linkIsLHS ? $this->def['join_key_lhs'] : $this->def['join_key_rhs'];
343         $joinTable = $this->getRelationshipTable();
344         $joinTableWithAlias = $joinTable;
345         $joinKey = $linkIsLHS ? $this->def['join_key_rhs'] : $this->def['join_key_lhs'];
346         $targetTable = $linkIsLHS ? $this->def['rhs_table'] : $this->def['lhs_table'];
347         $targetTableWithAlias = $targetTable;
348         $targetKey = $linkIsLHS ? $this->def['rhs_key'] : $this->def['lhs_key'];
349         $join_type= isset($params['join_type']) ? $params['join_type'] : ' INNER JOIN ';
350
351         $join = '';
352
353         //Set up any table aliases required
354         if (!empty($params['join_table_link_alias']))
355         {
356             $joinTableWithAlias = $joinTable . " ". $params['join_table_link_alias'];
357             $joinTable = $params['join_table_link_alias'];
358         }
359         if ( ! empty($params['join_table_alias']))
360         {
361             $targetTableWithAlias = $targetTable . " ". $params['join_table_alias'];
362             $targetTable = $params['join_table_alias'];
363         }
364
365         $join1 = "$startingTable.$startingKey=$joinTable.$startingJoinKey";
366         $join2 = "$targetTable.$targetKey=$joinTable.$joinKey";
367         $where = "";
368
369
370         //First join the relationship table
371         $join .= "$join_type $joinTableWithAlias ON $join1 AND $joinTable.deleted=0\n"
372         //Next add any role filters
373                . $this->getRoleWhere($joinTable) . "\n"
374         //Then finally join the related module's table
375                . "$join_type $targetTableWithAlias ON $join2 AND $targetTable.deleted=0\n";
376
377         if($return_array){
378             return array(
379                 'join' => $join,
380                 'type' => $this->type,
381                 'rel_key' => $joinKey,
382                 'join_tables' => array($joinTable, $targetTable),
383                 'where' => $where,
384                 'select' => "$targetTable.id",
385             );
386         }
387         return $join . $where;
388     }
389
390     /**
391      * Similar to getQuery or Get join, except this time we are starting from the related table and
392      * searching for items with id's matching the $link->focus->id
393      * @param  $link
394      * @param array $params
395      * @param bool $return_array
396      * @return String|Array
397      */
398     public function getSubpanelQuery($link, $params = array(), $return_array = false)
399     {
400         $targetIsLHS = $link->getSide() == REL_RHS;
401         $startingTable = $targetIsLHS ? $this->def['lhs_table'] : $this->def['rhs_table'];;
402         $startingKey = $targetIsLHS ? $this->def['lhs_key'] : $this->def['rhs_key'];
403         $startingJoinKey = $targetIsLHS ? $this->def['join_key_lhs'] : $this->def['join_key_rhs'];
404         $joinTable = $this->getRelationshipTable();
405         $joinTableWithAlias = $joinTable;
406         $joinKey = $targetIsLHS ? $this->def['join_key_rhs'] : $this->def['join_key_lhs'];
407         $targetKey = $targetIsLHS ? $this->def['rhs_key'] : $this->def['lhs_key'];
408         $join_type= isset($params['join_type']) ? $params['join_type'] : ' INNER JOIN ';
409
410         $query = '';
411
412         //Set up any table aliases required
413         if (!empty($params['join_table_link_alias']))
414         {
415             $joinTableWithAlias = $joinTable . " ". $params['join_table_link_alias'];
416             $joinTable = $params['join_table_link_alias'];
417         }
418
419         $where = "$startingTable.$startingKey=$joinTable.$startingJoinKey AND $joinTable.$joinKey='{$link->getFocus()->$targetKey}'";
420
421         //Check if we should ignore the role fileter;
422         $ignoreRole = !empty($params['ignore_role']);
423
424         //First join the relationship table
425         $query .= "$join_type $joinTableWithAlias ON $where AND $joinTable.deleted=0\n"
426         //Next add any role filters
427                . $this->getRoleWhere($joinTable, $ignoreRole) . "\n";
428
429         if (!empty($params['return_as_array'])) {
430             $return_array = true;
431         }
432         if($return_array){
433             return array(
434                 'join' => $query,
435                 'type' => $this->type,
436                 'rel_key' => $joinKey,
437                 'join_tables' => array($joinTable),
438                 'where' => "",
439                 'select' => " ",
440             );
441         }
442         return $query;
443
444     }
445
446     protected function getRoleFilterForJoin()
447     {
448         $ret = "";
449         if (!empty($this->relationship_role_column) && !$this->ignore_role_filter)
450         {
451             $ret .= " AND ".$this->getRelationshipTable().'.'.$this->relationship_role_column;
452             //role column value.
453             if (empty($this->relationship_role_column_value))
454             {
455                 $ret.=' IS NULL';
456             } else {
457                 $ret.= "='".$this->relationship_role_column_value."'";
458             }
459             $ret.= "\n";
460         }
461         return $ret;
462     }
463
464     /**
465      * @param  $lhs
466      * @param  $rhs
467      * @return bool
468      */
469     public function relationship_exists($lhs, $rhs)
470     {
471         $query = "SELECT * FROM {$this->getRelationshipTable()} WHERE {$this->join_key_lhs} = {$lhs->id} AND {$this->join_key_rhs} = {$rhs->id}";
472
473         //Roles can allow for multiple links between two records with different roles
474         $query .= $this->getRoleWhere() . " and deleted = 0";
475
476         $result = DBManagerFactory::getInstance()->query($query);
477         $row = $this->_db->fetchByAssoc($result);
478
479         if ($row == null) {
480             return false;
481         }
482         else {
483             return $row['id'];
484         }
485     }
486
487     /**
488      * @return Array - set of fields that uniquely identify an entry in this relationship
489      */
490     protected function getAlternateKeyFields()
491     {
492         $fields = array($this->join_key_lhs, $this->join_key_rhs);
493
494         //Roles can allow for multiple links between two records with different roles
495         if (!empty($this->def['relationship_role_column']) && !$this->ignore_role_filter)
496         {
497             $fields[] = $this->relationship_role_column;
498         }
499
500         return $fields;
501     }
502
503     public function getRelationshipTable()
504     {
505         if (!empty($this->def['table']))
506             return $this->def['table'];
507         else if(!empty($this->def['join_table']))
508             return $this->def['join_table'];
509
510         return false;
511     }
512
513     public function getFields()
514     {
515         if (!empty($this->def['fields']))
516             return $this->def['fields'];
517         $fields = array(
518             "id" => array('name' => 'id'),
519             'date_modified' => array('name' => 'date_modified'),
520             'modified_user_id' => array('name' => 'modified_user_id'),
521             'created_by' => array('name' => 'created_by'),
522             $this->def['join_key_lhs'] => array('name' => $this->def['join_key_lhs']),
523             $this->def['join_key_rhs'] => array('name' => $this->def['join_key_rhs'])
524         );
525         if (!empty($this->def['relationship_role_column']))
526         {
527             $fields[$this->def['relationship_role_column']] = array("name" => $this->def['relationship_role_column']);
528         }
529         $fields['deleted'] = array('name' => 'deleted');
530
531         return $fields;
532     }
533
534 }