]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - data/Relationships/M2MRelationship.php
Release 6.3.0beta4
[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         global $dictionary;
51
52         $this->def = $def;
53         $this->name = $def['name'];
54
55         $lhsModule = $def['lhs_module'];
56         $this->lhsLinkDef = $this->getLinkedDefForModuleByRelationship($lhsModule);
57         $this->lhsLink = $this->lhsLinkDef['name'];
58
59         $rhsModule = $def['rhs_module'];
60         $this->rhsLinkDef = $this->getLinkedDefForModuleByRelationship($rhsModule);
61         $this->rhsLink = $this->rhsLinkDef['name'];
62
63         $this->self_referencing = $lhsModule == $rhsModule;
64     }
65
66     /**
67      * Find the link entry for a particular relationship and module.
68      *
69      * @param $module
70      * @return array|bool
71      */
72     public function getLinkedDefForModuleByRelationship($module)
73     {
74         $results = VardefManager::getLinkFieldForRelationship( $module, BeanFactory::getBeanName($module), $this->name);
75         //Only a single link was found
76         if( isset($results['name']) )
77         {
78             return $results;
79         }
80         //Multiple links with same relationship name
81         else if( is_array($results) )
82         {
83             $GLOBALS['log']->error("Warning: Multiple links found for relationship {$this->name} within module {$module}");
84             return $this->getMostAppropriateLinkedDefinition($results);
85         }
86         else
87         {
88             return FALSE;
89         }
90     }
91
92     /**
93      * Find the most 'appropriate' link entry for a relationship/module in which there are multiple link entries with the
94      * same relationship name.
95      *
96      * @param $links
97      * @return bool
98      */
99     protected function getMostAppropriateLinkedDefinition($links)
100     {
101         foreach($links as $link)
102         {
103             if( isset($link['name']) && $link['name'] == $this->name )
104             {
105                 return $link;
106             }
107         }
108         //Unable to find an appropriate link, return nothing rather an invalid link.
109         $GLOBALS['log']->error("Unable to determine best appropriate link for relationship {$this->name}");
110         return FALSE;
111     }
112     /**
113      * @param  $lhs SugarBean left side bean to add to the relationship.
114      * @param  $rhs SugarBean right side bean to add to the relationship.
115      * @param  $additionalFields key=>value pairs of fields to save on the relationship
116      * @return boolean true if successful
117      */
118     public function add($lhs, $rhs, $additionalFields = array())
119     {
120         $lhsLinkName = $this->lhsLink;
121         $rhsLinkName = $this->rhsLink;
122
123         if (empty($lhs->$lhsLinkName) && !$lhs->load_relationship($lhsLinkName))
124         {
125             $lhsClass = get_class($lhs);
126             $GLOBALS['log']->fatal("could not load LHS $lhsLinkName in $lhsClass");
127             return false;
128         }
129         if (empty($rhs->$rhsLinkName) && !$rhs->load_relationship($rhsLinkName))
130         {
131             $rhsClass = get_class($rhs);
132             $GLOBALS['log']->fatal("could not load RHS $rhsLinkName in $rhsClass");
133             return false;
134         }
135
136         //Many to many has no additional logic, so just add a new row to the table and notify the beans.
137         $dataToInsert = $this->getRowToInsert($lhs, $rhs);
138         $dataToInsert = array_merge($dataToInsert, $additionalFields);
139
140         $this->addRow($dataToInsert);
141
142         if (empty($_SESSION['disable_workflow']) || $_SESSION['disable_workflow'] != "Yes")
143         {
144             $lhs->$lhsLinkName->addBean($rhs);
145             $rhs->$rhsLinkName->addBean($lhs);
146
147             $this->callAfterAdd($lhs, $rhs, $lhsLinkName);
148             $this->callAfterAdd($rhs, $lhs, $rhsLinkName);
149         }
150     }
151
152     protected function getRowToInsert($lhs, $rhs)
153     {
154         $row = array(
155             "id" => create_guid(),
156             $this->def['join_key_lhs'] => $lhs->id,
157             $this->def['join_key_rhs'] => $rhs->id,
158             'date_modified' => TimeDate::getInstance()->getNow()->asDb(),
159             'deleted' => 0,
160         );
161
162
163         if (!empty($this->def['relationship_role_column']) && !empty($this->def['relationship_role_column_value']) && !$this->ignore_role_filter )
164         {
165             $row[$this->relationship_role_column] = $this->relationship_role_column_value;
166         }
167
168         if (!empty($this->def['fields']))
169         {
170             foreach($this->def['fields'] as $fieldDef)
171             {
172                 if (!empty($fieldDef['name']) && !isset($row[$fieldDef['name']]) && !empty($fieldDef['default']))
173                 {
174                     $row[$fieldDef['name']] = $fieldDef['default'];
175                 }
176             }
177         }
178
179         return $row;
180     }
181
182
183     public function remove($lhs, $rhs)
184     {
185         $lhsLinkName = $this->lhsLink;
186         $rhsLinkName = $this->rhsLink;
187
188         if (!($lhs instanceof SugarBean)) {
189             $GLOBALS['log']->fatal("LHS is not a SugarBean object");
190             return false;
191         }
192         if (!($rhs instanceof SugarBean)) {
193             $GLOBALS['log']->fatal("RHS is not a SugarBean object");
194             return false;
195         }
196         if (empty($lhs->$lhsLinkName) && !$lhs->load_relationship($lhsLinkName))
197         {
198             $GLOBALS['log']->fatal("could not load LHS $lhsLinkName");
199             return false;
200         }
201         if (empty($rhs->$rhsLinkName) && !$rhs->load_relationship($rhsLinkName))
202         {
203             $GLOBALS['log']->fatal("could not load RHS $rhsLinkName");
204             return false;
205         }
206
207         $dataToRemove = array(
208             $this->def['join_key_lhs'] => $lhs->id,
209             $this->def['join_key_rhs'] => $rhs->id
210         );
211
212         $this->removeRow($dataToRemove);
213
214         if (empty($_SESSION['disable_workflow']) || $_SESSION['disable_workflow'] != "Yes")
215         {
216             $lhs->$lhsLinkName->load();
217             $rhs->$rhsLinkName->load();
218
219             $this->callAfterDelete($lhs, $rhs, $lhsLinkName);
220             $this->callAfterDelete($rhs, $lhs, $rhsLinkName);
221         }
222     }
223
224     /**
225      * @param  $link Link2 loads the relationship for this link.
226      * @return void
227      */
228     public function load($link)
229     {
230         $db = DBManagerFactory::getInstance();
231         $query = $this->getQuery($link);
232         $result = $db->query($query);
233         $beans = Array();
234         $rows = Array();
235         $relatedModule = $link->getSide() == REL_LHS ? $this->def['rhs_module'] : $this->def['lhs_module'];
236         $idField = $link->getSide() == REL_LHS ? $this->def['join_key_rhs'] : $this->def['join_key_lhs'];
237         while ($row = $db->fetchByAssoc($result))
238         {
239             $id = $row[$idField];
240             $rows[$id] = $row;
241         }
242         return array("rows" => $rows);
243     }
244
245     public function getQuery($link, $params = array())
246     {
247         if ($link->getSide() == REL_LHS) {
248             $knownKey = $this->def['join_key_lhs'];
249             $targetKey = $this->def['join_key_rhs'];
250         }
251         else
252         {
253             $knownKey = $this->def['join_key_rhs'];
254             $targetKey = $this->def['join_key_lhs'];
255         }
256         $rel_table = $this->getRelationshipTable();
257
258         if (!$this->self_referencing)
259         {
260             $where = "$rel_table.$knownKey = '{$link->getFocus()->id}'";
261         }
262         else
263         {
264             $where = "($rel_table.{$this->def['join_key_rhs']} = '{$link->getFocus()->id}' OR $rel_table.{$this->def['join_key_lhs']} = '{$link->getFocus()->id}')";
265         }
266
267         if (empty($params['return_as_array'])) {
268             return "SELECT $targetKey FROM $rel_table WHERE $where AND deleted=0";
269         }
270         else
271         {
272             return array(
273                 'select' => "SELECT $targetKey id",
274                 'from' => "FROM $rel_table",
275                 'where' => "WHERE $where AND $rel_table.deleted=0",
276             );
277         }
278     }
279
280     public function getJoin($link, $params = array(), $return_array = false)
281     {
282         $linkIsLHS = $link->getSide() == REL_LHS;
283         if ($linkIsLHS) {
284             $startingTable = (empty($params['left_join_table_alias']) ? $link->getFocus()->table_name : $params['left_join_table_alias']);
285         } else {
286             $startingTable = (empty($params['right_join_table_alias']) ? $link->getFocus()->table_name : $params['right_join_table_alias']);
287         }
288
289         $startingKey = $linkIsLHS ? $this->def['lhs_key'] : $this->def['rhs_key'];
290         $startingJoinKey = $linkIsLHS ? $this->def['join_key_lhs'] : $this->def['join_key_rhs'];
291         $joinTable = $this->getRelationshipTable();
292         $joinTableWithAlias = $joinTable;
293         $joinKey = $linkIsLHS ? $this->def['join_key_rhs'] : $this->def['join_key_lhs'];
294         $targetTable = $linkIsLHS ? $this->def['rhs_table'] : $this->def['lhs_table'];
295         $targetTableWithAlias = $targetTable;
296         $targetKey = $linkIsLHS ? $this->def['rhs_key'] : $this->def['lhs_key'];
297         $join_type= isset($params['join_type']) ? $params['join_type'] : ' INNER JOIN ';
298
299         $join = '';
300
301         //Set up any table aliases required
302         if (!empty($params['join_table_link_alias']))
303         {
304             $joinTableWithAlias = $joinTable . " ". $params['join_table_link_alias'];
305             $joinTable = $params['join_table_link_alias'];
306         }
307         if ( ! empty($params['join_table_alias']))
308         {
309             $targetTableWithAlias = $targetTable . " ". $params['join_table_alias'];
310             $targetTable = $params['join_table_alias'];
311         }
312
313         if (!$this->self_referencing)
314         {
315             $join1 = "$startingTable.$startingKey=$joinTable.$startingJoinKey";
316             $join2 = "$targetTable.$targetKey=$joinTable.$joinKey";
317             $where = "";
318         }
319         else
320         {
321             $join1 = "($startingTable.$startingKey=$joinTable.{$this->def['join_key_rhs']} OR $startingTable.$startingKey=$joinTable.{$this->def['join_key_rhs']})";
322             $join2 = "($targetTable.$targetKey=$joinTable.{$this->def['join_key_rhs']} OR $targetTable.$targetKey=$joinTable.{$this->def['join_key_rhs']})";
323             $where = "(($startingTable.$startingKey=$joinTable.{$this->def['join_key_rhs']} AND $joinTable.{$this->def['join_key_lhs']}='{$link->getFocus()->$targetKey}') OR "
324                    . "($startingTable.$startingKey=$joinTable.{$this->def['join_key_lhs']} AND $joinTable.{$this->def['join_key_rhs']}='{$link->getFocus()->$targetKey}'))";
325         }
326
327
328         //First join the relationship table
329         $join .= "$join_type $joinTableWithAlias ON $join1 AND $joinTable.deleted=0\n"
330         //Next add any role filters
331                . $this->getRoleFilterForJoin() . "\n"
332         //Then finally join the related module's table
333                . "$join_type $targetTableWithAlias ON $join2 AND $targetTable.deleted=0\n";
334
335                 if($return_array){
336                         return array(
337                 'join' => $join,
338                 'type' => $this->type,
339                 'rel_key' => $joinKey,
340                 'join_tables' => array($joinTable, $targetTable),
341                 'where' => $where,
342                 'select' => "$targetTable.id",
343             );
344                 }
345                 return $join . $where;
346     }
347
348     /**
349      * Similar to getQuery or Get join, except this time we are starting from the related table and
350      * searching for items with id's matching the $link->focus->id
351      * @param  $link
352      * @param array $params
353      * @param bool $return_array
354      * @return void
355      */
356     public function getSubpanelQuery($link, $params = array(), $return_array = false)
357     {
358         $targetIsLHS = $link->getSide() == REL_RHS;
359         $startingTable = $targetIsLHS ? $this->def['lhs_table'] : $this->def['rhs_table'];;
360         $startingKey = $targetIsLHS ? $this->def['lhs_key'] : $this->def['rhs_key'];
361         $startingJoinKey = $targetIsLHS ? $this->def['join_key_lhs'] : $this->def['join_key_rhs'];
362         $joinTable = $this->getRelationshipTable();
363         $joinTableWithAlias = $joinTable;
364         $joinKey = $targetIsLHS ? $this->def['join_key_rhs'] : $this->def['join_key_lhs'];
365         $targetKey = $targetIsLHS ? $this->def['rhs_key'] : $this->def['lhs_key'];
366         $join_type= isset($params['join_type']) ? $params['join_type'] : ' INNER JOIN ';
367
368         $query = '';
369
370         //Set up any table aliases required
371         if (!empty($params['join_table_link_alias']))
372         {
373             $joinTableWithAlias = $joinTable . " ". $params['join_table_link_alias'];
374             $joinTable = $params['join_table_link_alias'];
375         }
376
377         if (!$this->self_referencing)
378         {
379             $where = "$startingTable.$startingKey=$joinTable.$startingJoinKey AND $joinTable.$joinKey='{$link->getFocus()->$targetKey}'";
380         }
381         else
382         {
383             $where = "(($startingTable.$startingKey=$joinTable.{$this->def['join_key_rhs']} AND $joinTable.{$this->def['join_key_lhs']}='{$link->getFocus()->$targetKey}') OR "
384                    . "($startingTable.$startingKey=$joinTable.{$this->def['join_key_lhs']} AND $joinTable.{$this->def['join_key_rhs']}='{$link->getFocus()->$targetKey}'))";
385         }
386
387         //First join the relationship table
388         $query .= "$join_type $joinTableWithAlias ON $where AND $joinTable.deleted=0\n"
389         //Next add any role filters
390                . $this->getRoleFilterForJoin() . "\n";
391         
392                 if (!empty($params['return_as_array'])) {
393             $return_array = true;
394         }
395         if($return_array){
396                         return array(
397                 'join' => $query,
398                 'type' => $this->type,
399                 'rel_key' => $joinKey,
400                 'join_tables' => array($joinTable),
401                 'where' => "",
402                 'select' => " ",
403             );
404                 }
405                 return $query;
406
407     }
408
409     protected function getRoleFilterForJoin()
410     {
411         $ret = "";
412         if (!empty($this->relationship_role_column) && !$this->ignore_role_filter)
413         {
414             $ret .= " AND ".$this->getRelationshipTable().'.'.$this->relationship_role_column;
415             //role column value.
416             if (empty($this->relationship_role_column_value))
417             {
418                 $ret.=' IS NULL';
419             } else {
420                 $ret.= "='".$this->relationship_role_column_value."'";
421             }
422             $ret.= "\n";
423         }
424         return $ret;
425     }
426
427     /**
428      * @param  $lhs
429      * @param  $rhs
430      * @return bool
431      */
432     public function relationship_exists($lhs, $rhs)
433     {
434         $query = "SELECT * FROM {$this->getRelationshipTable()} WHERE {$this->join_key_lhs} = {$lhs->id} AND {$this->join_key_rhs} = {$rhs->id}";
435
436         //Roles can allow for multiple links between two records with different roles
437         $query .= $this->getRoleFilterForJoin() . " and deleted = 0";
438
439         $result = DBManagerFactory::getInstance()->query($query);
440         $row = $this->_db->fetchByAssoc($result);
441
442                 if ($row == null) {
443                         return false;
444                 }
445                 else {
446                         return $row['id'];
447                 }
448     }
449
450     /**
451      * @return Array - set of fields that uniquely identify an entry in this relationship
452      */
453     protected function getAlternateKeyFields()
454     {
455         $fields = array($this->join_key_lhs, $this->join_key_rhs);
456
457         //Roles can allow for multiple links between two records with different roles
458         if (!empty($this->def['relationship_role_column']) && !$this->ignore_role_filter)
459         {
460             $fields[] = $this->relationship_role_column;
461         }
462
463         return $fields;
464     }
465
466     public function getRelationshipTable()
467     {
468         if (!empty($this->def['table']))
469             return $this->def['table'];
470         else if(!empty($this->def['join_table']))
471             return $this->def['join_table'];
472
473         return false;
474     }
475
476     public function getFields()
477     {
478         if (!empty($this->def['fields']))
479             return $this->def['fields'];
480         $fields = array(
481             "id" => array('name' => 'id'),
482             'date_modified' => array('name' => 'date_modified'),
483             'modified_user_id' => array('name' => 'modified_user_id'),
484             'created_by' => array('name' => 'created_by'),
485             $this->def['join_key_lhs'] => array('name' => $this->def['join_key_lhs']),
486             $this->def['join_key_rhs'] => array('name' => $this->def['join_key_rhs'])
487         );
488         if (!empty($this->def['relationship_role_column']))
489         {
490             $fields[$this->def['relationship_role_column']] = array("name" => $this->def['relationship_role_column']);
491         }
492         $fields['deleted'] = array('name' => 'deleted');
493
494         return $fields;
495     }
496
497 }