]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - data/Relationships/M2MRelationship.php
Release 6.3.0beta2
[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         if (!empty($this->def['fields']))
127         {
128             foreach($this->def['fields'] as $fieldDef)
129             {
130                 if (!empty($fieldDef['name']) && !isset($row[$fieldDef['name']]) && !empty($fieldDef['default']))
131                 {
132                     $row[$fieldDef['name']] = $fieldDef['default'];
133                 }
134             }
135         }
136
137         return $row;
138     }
139
140
141     public function remove($lhs, $rhs)
142     {
143         $lhsLinkName = $this->lhsLink;
144         $rhsLinkName = $this->rhsLink;
145
146         if (!($lhs instanceof SugarBean)) {
147             $GLOBALS['log']->fatal("LHS is not a SugarBean object");
148             return false;
149         }
150         if (!($rhs instanceof SugarBean)) {
151             $GLOBALS['log']->fatal("RHS is not a SugarBean object");
152             return false;
153         }
154         if (empty($lhs->$lhsLinkName) && !$lhs->load_relationship($lhsLinkName))
155         {
156             $GLOBALS['log']->fatal("could not load LHS $lhsLinkName");
157             return false;
158         }
159         if (empty($rhs->$rhsLinkName) && !$rhs->load_relationship($rhsLinkName))
160         {
161             $GLOBALS['log']->fatal("could not load RHS $rhsLinkName");
162             return false;
163         }
164
165         $dataToRemove = array(
166             $this->def['join_key_lhs'] => $lhs->id,
167             $this->def['join_key_rhs'] => $rhs->id
168         );
169
170         $this->removeRow($dataToRemove);
171
172         if (empty($_SESSION['disable_workflow']) || $_SESSION['disable_workflow'] != "Yes")
173         {
174             $lhs->$lhsLinkName->load();
175             $rhs->$rhsLinkName->load();
176
177             $this->callAfterDelete($lhs, $rhs, $lhsLinkName);
178             $this->callAfterDelete($rhs, $lhs, $rhsLinkName);
179         }
180     }
181
182     /**
183      * @param  $link Link2 loads the relationship for this link.
184      * @return void
185      */
186     public function load($link)
187     {
188         $db = DBManagerFactory::getInstance();
189         $query = $this->getQuery($link);
190         $result = $db->query($query);
191         $beans = Array();
192         $rows = Array();
193         $relatedModule = $link->getSide() == REL_LHS ? $this->def['rhs_module'] : $this->def['lhs_module'];
194         $idField = $link->getSide() == REL_LHS ? $this->def['join_key_rhs'] : $this->def['join_key_lhs'];
195         while ($row = $db->fetchByAssoc($result))
196         {
197             $id = $row[$idField];
198             $rows[$id] = $row;
199         }
200         return array("rows" => $rows);
201     }
202
203     public function getQuery($link, $params = array())
204     {
205         if ($link->getSide() == REL_LHS) {
206             $knownKey = $this->def['join_key_lhs'];
207             $targetKey = $this->def['join_key_rhs'];
208         }
209         else
210         {
211             $knownKey = $this->def['join_key_rhs'];
212             $targetKey = $this->def['join_key_lhs'];
213         }
214         $rel_table = $this->getRelationshipTable();
215
216         if (!$this->self_referencing)
217         {
218             $where = "$rel_table.$knownKey = '{$link->getFocus()->id}'";
219         }
220         else
221         {
222             $where = "($rel_table.{$this->def['join_key_rhs']} = '{$link->getFocus()->id}' OR $rel_table.{$this->def['join_key_lhs']} = '{$link->getFocus()->id}')";
223         }
224
225         if (empty($params['return_as_array'])) {
226             return "SELECT $targetKey FROM $rel_table WHERE $where AND deleted=0";
227         }
228         else
229         {
230             return array(
231                 'select' => "SELECT $targetKey id",
232                 'from' => "FROM $rel_table",
233                 'where' => "WHERE $where AND $rel_table.deleted=0",
234             );
235         }
236     }
237
238     public function getJoin($link, $params = array(), $return_array = false)
239     {
240         $linkIsLHS = $link->getSide() == REL_LHS;
241         $startingTable = $link->getFocus()->table_name;
242         $startingKey = $linkIsLHS ? $this->def['lhs_key'] : $this->def['rhs_key'];
243         $startingJoinKey = $linkIsLHS ? $this->def['join_key_lhs'] : $this->def['join_key_rhs'];
244         $joinTable = $this->getRelationshipTable();
245         $joinTableWithAlias = $joinTable;
246         $joinKey = $linkIsLHS ? $this->def['join_key_rhs'] : $this->def['join_key_lhs'];
247         $targetTable = $linkIsLHS ? $this->def['rhs_table'] : $this->def['lhs_table'];
248         $targetTableWithAlias = $targetTable;
249         $targetKey = $linkIsLHS ? $this->def['rhs_key'] : $this->def['lhs_key'];
250         $join_type= isset($params['join_type']) ? $params['join_type'] : ' INNER JOIN ';
251
252         $join = '';
253
254         //Set up any table aliases required
255         if (!empty($params['join_table_link_alias']))
256         {
257             $joinTableWithAlias = $joinTable . " ". $params['join_table_link_alias'];
258             $joinTable = $params['join_table_link_alias'];
259         }
260         if ( ! empty($params['join_table_alias']))
261         {
262             $targetTableWithAlias = $targetTable . " ". $params['join_table_alias'];
263             $targetTable = $params['join_table_alias'];
264         }
265
266         if (!$this->self_referencing)
267         {
268             $join1 = "$startingTable.$startingKey=$joinTable.$startingJoinKey";
269             $join2 = "$targetTable.$targetKey=$joinTable.$joinKey";
270             $where = "";
271         }
272         else
273         {
274             $join1 = "($startingTable.$startingKey=$joinTable.{$this->def['join_key_rhs']} OR $startingTable.$startingKey=$joinTable.{$this->def['join_key_rhs']})";
275             $join2 = "($targetTable.$targetKey=$joinTable.{$this->def['join_key_rhs']} OR $targetTable.$targetKey=$joinTable.{$this->def['join_key_rhs']})";
276             $where = "($startingTable.$startingKey=$joinTable.{$this->def['join_key_rhs']} AND $joinTable.{$this->def['join_key_lhs']}='{$link->getFocus()->$targetKey}') OR "
277                    . "($startingTable.$startingKey=$joinTable.{$this->def['join_key_lhs']} AND $joinTable.{$this->def['join_key_rhs']}='{$link->getFocus()->$targetKey}')";
278         }
279
280
281         //First join the relationship table
282         $join .= "$join_type $joinTableWithAlias ON $join1 AND $joinTable.deleted=0\n"
283         //Next add any role filters
284                . $this->getRoleFilterForJoin() . "\n"
285         //Then finally join the related module's table
286                . "$join_type $targetTableWithAlias ON $join2 AND $targetTable.deleted=0\n";
287
288                 if($return_array){
289                         return array(
290                 'join' => $join,
291                 'type' => $this->type,
292                 'rel_key' => $joinKey,
293                 'join_tables' => array($joinTable, $targetTable),
294                 'where' => $where,
295                 'select' => "$targetTable.id",
296             );
297                 }
298                 return $join . $where;
299     }
300
301     /**
302      * Similar to getQuery or Get join, except this time we are starting from the related table and
303      * searching for items with id's matching the $link->focus->id
304      * @param  $link
305      * @param array $params
306      * @param bool $return_array
307      * @return void
308      */
309     public function getSubpanelQuery($link, $params = array(), $return_array = false)
310     {
311         $targetIsLHS = $link->getSide() == REL_RHS;
312         $startingTable = $targetIsLHS ? $this->def['lhs_table'] : $this->def['rhs_table'];;
313         $startingKey = $targetIsLHS ? $this->def['lhs_key'] : $this->def['rhs_key'];
314         $startingJoinKey = $targetIsLHS ? $this->def['join_key_lhs'] : $this->def['join_key_rhs'];
315         $joinTable = $this->getRelationshipTable();
316         $joinTableWithAlias = $joinTable;
317         $joinKey = $targetIsLHS ? $this->def['join_key_rhs'] : $this->def['join_key_lhs'];
318         $targetKey = $targetIsLHS ? $this->def['rhs_key'] : $this->def['lhs_key'];
319         $join_type= isset($params['join_type']) ? $params['join_type'] : ' INNER JOIN ';
320
321         $query = '';
322
323         //Set up any table aliases required
324         if (!empty($params['join_table_link_alias']))
325         {
326             $joinTableWithAlias = $joinTable . " ". $params['join_table_link_alias'];
327             $joinTable = $params['join_table_link_alias'];
328         }
329
330         if (!$this->self_referencing)
331         {
332             $where = "$startingTable.$startingKey=$joinTable.$startingJoinKey AND $joinTable.$joinKey='{$link->getFocus()->$targetKey}'";
333         }
334         else
335         {
336             $where = "($startingTable.$startingKey=$joinTable.{$this->def['join_key_rhs']} AND $joinTable.{$this->def['join_key_lhs']}='{$link->getFocus()->$targetKey}') OR "
337                    . "($startingTable.$startingKey=$joinTable.{$this->def['join_key_lhs']} AND $joinTable.{$this->def['join_key_rhs']}='{$link->getFocus()->$targetKey}')";
338         }
339
340         //First join the relationship table
341         $query .= "$join_type $joinTableWithAlias ON $where AND $joinTable.deleted=0\n"
342         //Next add any role filters
343                . $this->getRoleFilterForJoin() . "\n";
344         
345                 if (!empty($params['return_as_array'])) {
346             $return_array = true;
347         }
348         if($return_array){
349                         return array(
350                 'join' => $query,
351                 'type' => $this->type,
352                 'rel_key' => $joinKey,
353                 'join_tables' => array($joinTable),
354                 'where' => "",
355                 'select' => " ",
356             );
357                 }
358                 return $query;
359
360     }
361
362     protected function getRoleFilterForJoin()
363     {
364         $ret = "";
365         if (!empty($this->relationship_role_column) && !$this->ignore_role_filter)
366         {
367             $ret .= " AND ".$this->getRelationshipTable().'.'.$this->relationship_role_column;
368             //role column value.
369             if (empty($this->relationship_role_column_value))
370             {
371                 $ret.=' IS NULL';
372             } else {
373                 $ret.= "='".$this->relationship_role_column_value."'";
374             }
375             $ret.= "\n";
376         }
377         return $ret;
378     }
379
380     /**
381      * @param  $lhs
382      * @param  $rhs
383      * @return bool
384      */
385     public function relationship_exists($lhs, $rhs)
386     {
387         $query = "SELECT * FROM {$this->getRelationshipTable()} WHERE {$this->join_key_lhs} = {$lhs->id} AND {$this->join_key_rhs} = {$rhs->id}";
388
389         //Roles can allow for multiple links between two records with different roles
390         $query .= $this->getRoleFilterForJoin() . " and deleted = 0";
391
392         $result = DBManagerFactory::getInstance()->query($query);
393         $row = $this->_db->fetchByAssoc($result);
394
395                 if ($row == null) {
396                         return false;
397                 }
398                 else {
399                         return $row['id'];
400                 }
401     }
402
403     /**
404      * @return Array - set of fields that uniquely identify an entry in this relationship
405      */
406     protected function getAlternateKeyFields()
407     {
408         $fields = array($this->join_key_lhs, $this->join_key_rhs);
409
410         //Roles can allow for multiple links between two records with different roles
411         if (!empty($this->def['relationship_role_column']) && !$this->ignore_role_filter)
412         {
413             $fields[] = $this->relationship_role_column;
414         }
415
416         return $fields;
417     }
418
419     public function getRelationshipTable()
420     {
421         if (!empty($this->def['table']))
422             return $this->def['table'];
423         else if(!empty($this->def['join_table']))
424             return $this->def['join_table'];
425
426         return false;
427     }
428
429     public function getFields()
430     {
431         if (!empty($this->def['fields']))
432             return $this->def['fields'];
433         $fields = array(
434             "id" => array('name' => 'id'),
435             'date_modified' => array('name' => 'date_modified'),
436             'modified_user_id' => array('name' => 'modified_user_id'),
437             'created_by' => array('name' => 'created_by'),
438             $this->def['join_key_lhs'] => array('name' => $this->def['join_key_lhs']),
439             $this->def['join_key_rhs'] => array('name' => $this->def['join_key_rhs'])
440         );
441         if (!empty($this->def['relationship_role_column']))
442         {
443             $fields[$this->def['relationship_role_column']] = array("name" => $this->def['relationship_role_column']);
444         }
445         $fields['deleted'] = array('name' => 'deleted');
446
447         return $fields;
448     }
449
450 }