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