]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - data/Relationships/SugarRelationship.php
Release 6.4.0
[Github/sugarcrm.git] / data / Relationships / SugarRelationship.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 global $dictionary;
40 //Load all relationship metadata
41 include_once("modules/TableDictionary.php");
42 require_once("data/BeanFactory.php");
43
44
45 define('REL_LHS','LHS');
46 define('REL_RHS','RHS');
47 define('REL_BOTH','BOTH_SIDES');
48 define('REL_MANY_MANY', 'many-to-many');
49 define('REL_ONE_MANY', 'one-to-many');
50 define('REL_ONE_ONE', 'one-to-one');
51 /**
52  * A relationship is between two modules.
53  * It contains at least two links.
54  * Each link represents a connection from one record to the records linked in this relationship.
55  * Links have a context(focus) bean while relationships do not.
56  * @api
57  */
58 abstract class SugarRelationship
59 {
60     protected $def;
61     protected $lhsLink;
62     protected $rhsLink;
63     protected $ignore_role_filter = false;
64     protected $self_referencing = false; //A relationship is self referencing when LHS module = RHS Module
65
66     protected static $beansToResave = array();
67
68     public abstract function add($lhs, $rhs, $additionalFields = array());
69
70     /**
71      * @abstract
72      * @param  $lhs SugarBean
73      * @param  $rhs SugarBean
74      * @return void
75      */
76     public abstract function remove($lhs, $rhs);
77
78     /**
79      * @abstract
80      * @param $link Link2 loads the rows for this relationship that match the given link
81      * @return void
82      */
83     public abstract function load($link);
84
85     /**
86      * Gets the query to load a link.
87      * This is currently public, but should prob be made protected later.
88      * See Link2->getQuery
89      * @abstract
90      * @param  $link Link Object to get query for.
91      * @return string|array query used to load this relationship
92      */
93     public abstract function getQuery($link, $params = array());
94
95     /**
96      * @abstract
97      * @param Link2 $link
98      * @return string|array the query to join against the related modules table for the given link.
99      */
100     public abstract function getJoin($link);
101
102     /**
103      * @abstract
104      * @param SugarBean $lhs
105      * @param SugarBean $rhs
106      * @return bool
107      */
108     public abstract function relationship_exists($lhs, $rhs);
109
110     /**
111      * @abstract
112      * @return string name of the table for this relationship
113      */
114     public abstract function getRelationshipTable();
115
116     /**
117      * @param  $link Link2 removes all the beans associated with this link from the relationship
118      * @return void
119      */
120     public function removeAll($link)
121     {
122         $focus = $link->getFocus();
123         $related = $link->getBeans();
124         foreach($related as $relBean)
125         {
126             if (empty($relBean->id)) {
127                 continue;
128             }
129
130             if ($link->getSide() == REL_LHS)
131                 $this->remove($focus, $relBean);
132             else
133                 $this->remove($relBean, $focus);
134         }
135     }
136
137     /**
138      * @param $rowID id of SugarBean to remove from the relationship
139      * @return void
140      */
141     public function removeById($rowID){
142         $this->removeRow(array("id" => $rowID));
143     }
144
145     /**
146      * @return string name of right hand side module.
147      */
148     public function getRHSModule()
149     {
150         return $this->def['rhs_module'];
151     }
152
153     /**
154      * @return string name of left hand side module.
155      */
156     public function getLHSModule()
157     {
158         return $this->def['lhs_module'];
159     }
160
161     /**
162      * @return String left link in relationship.
163      */
164     public function getLHSLink()
165     {
166         return $this->lhsLink;
167     }
168
169     /**
170      * @return String right link in relationship.
171      */
172     public function getRHSLink()
173     {
174         return $this->rhsLink;
175     }
176
177     /**
178      * @return array names of fields stored on the relationship
179      */
180     public function getFields()
181     {
182         return isset($this->def['fields']) ? $this->def['fields'] : array();
183     }
184
185     /**
186      * @param array $row values to be inserted into the relationship
187      * @return bool|void null if new row was inserted and true if an exesting row was updated
188      */
189     protected function addRow($row)
190     {
191         $existing = $this->checkExisting($row);
192         if (!empty($existing)) //Update the existing row, overriding the values with those passed in
193             return $this->updateRow($existing['id'], array_merge($existing, $row));
194
195         $values = array();
196         foreach($this->getFields() as  $def)
197         {
198             $field = $def['name'];
199             if (isset($row[$field]))
200                 $values[$field] = "'{$row[$field]}'";
201             else
202                 $values[$field] = "''";
203         }
204         $columns = implode(',', array_keys($values));
205         $values = implode(',', $values);
206         if (!empty($values))
207         {
208             $query = "INSERT INTO {$this->getRelationshipTable()} ($columns) VALUES ($values)";
209             DBManagerFactory::getInstance()->query($query);
210         }
211     }
212
213     /**
214      * @param $id id of row to update
215      * @param $values values to insert into row
216      * @return resource result of update satatement
217      */
218     protected function updateRow($id, $values)
219     {
220         $newVals = array();
221         //Unset the ID since we are using it to update the row
222         if (isset($values['id'])) unset($values['id']);
223         foreach($values as $field => $val)
224         {
225             $newVals[] = "$field='$val'";
226         }
227
228         $newVals = implode(",",$newVals);
229
230         $query = "UPDATE {$this->getRelationshipTable()} set $newVals WHERE id='$id'";
231
232         return DBManagerFactory::getInstance()->query($query);
233     }
234
235     /**
236      * Removes one or more rows from the relationship table
237      * @param $where array of field=>value pairs to match
238      * @return bool|resource
239      */
240     protected function removeRow($where)
241     {
242         if (empty($where))
243             return false;
244
245         $date_modified = TimeDate::getInstance()->getNow()->asDb();
246         $stringSets = array();
247         foreach ($where as $field => $val)
248         {
249             $stringSets[] = "$field = '$val'";
250         }
251         $whereString = "WHERE " . implode(" AND ", $stringSets);
252
253         $query = "UPDATE {$this->getRelationshipTable()} set deleted=1 , date_modified = '$date_modified' $whereString";
254
255         return DBManagerFactory::getInstance()->query($query);
256
257     }
258
259     /**
260      * Checks for an existing row who's keys match the one passed in.
261      * @param  $row
262      * @return array|bool returns false if now row is found, otherwise the row is returned
263      */
264     protected function checkExisting($row)
265     {
266         $leftIDName = $this->def['join_key_lhs'];
267         $rightIDName = $this->def['join_key_rhs'];
268         if (empty($row[$leftIDName]) ||  empty($row[$rightIDName]))
269             return false;
270
271         $leftID = $row[$leftIDName];
272         $rightID = $row[$rightIDName];
273         //Check the relationship role as well
274         $roleCheck = $this->getRoleWhere();
275
276         $query = "SELECT * FROM {$this->getRelationshipTable()} WHERE $leftIDName='$leftID' AND $rightIDName='$rightID' $roleCheck AND deleted=0";
277
278         $db = DBManagerFactory::getInstance();
279         $result = $db->query($query);
280         $row = $db->fetchByAssoc($result);
281         if (!empty($row))
282         {
283             return $row;
284         } else{
285             return false;
286         }
287     }
288
289     /**
290      * Gets the relationship role column check for the where clause
291      * @param string $table
292      * @return string
293      */
294     protected function getRoleWhere($table = "", $ignore_role_filter = false)
295     {
296         $ignore_role_filter = $ignore_role_filter || $this->ignore_role_filter;
297         $roleCheck = "";
298         if (empty ($table))
299             $table = $this->getRelationshipTable();
300         if (!empty($this->def['relationship_role_column']) && !empty($this->def["relationship_role_column_value"]) && !$ignore_role_filter )
301         {
302             if (empty($table))
303                 $roleCheck = " AND $this->relationship_role_column";
304             else
305                 $roleCheck = " AND $table.{$this->relationship_role_column}";
306             //role column value.
307             if (empty($this->def['relationship_role_column_value']))
308             {
309                 $roleCheck.=' IS NULL';
310             } else {
311                 $roleCheck.= " = '$this->relationship_role_column_value'";
312             }
313         }
314         return $roleCheck;
315     }
316
317     /**
318      * @param SugarBean $focus base bean the hooks is triggered from
319      * @param SugarBean $related bean being added/removed/updated from relationship
320      * @param string $link_name name of link being triggerd
321      * @return array base arguments to pass to relationship logic hooks
322      */
323     protected function getCustomLogicArguments($focus, $related, $link_name)
324     {
325         $custom_logic_arguments = array();
326         $custom_logic_arguments['id'] = $focus->id;
327         $custom_logic_arguments['related_id'] = $related->id;
328         $custom_logic_arguments['module'] = $focus->module_dir;
329         $custom_logic_arguments['related_module'] = $related->module_dir;
330         $custom_logic_arguments['link'] = $link_name;
331         $custom_logic_arguments['relationship'] = $this->name;
332
333         return $custom_logic_arguments;
334     }
335
336     /**
337      * Call the after add logic hook for a given link
338      * @param  SugarBean $focus base bean the hooks is triggered from
339      * @param  SugarBean $related bean being added/removed/updated from relationship
340      * @param string $link_name name of link being triggerd
341      * @return void
342      */
343     protected function callAfterAdd($focus, $related, $link_name="")
344     {
345         $custom_logic_arguments = $this->getCustomLogicArguments($focus, $related, $link_name);
346         $focus->call_custom_logic('after_relationship_add', $custom_logic_arguments);
347     }
348
349     /**
350      * @param  SugarBean $focus
351      * @param  SugarBean $related
352      * @param string $link_name
353      * @return void
354      */
355     protected function callAfterDelete($focus, $related, $link_name="")
356     {
357         $custom_logic_arguments = $this->getCustomLogicArguments($focus, $related, $link_name);
358         $focus->call_custom_logic('after_relationship_delete', $custom_logic_arguments);
359     }
360
361     /**
362      * Adds a realted Bean to the list to be resaved along with the current bean.
363      * @static
364      * @param  SugarBean $bean
365      * @return void
366      */
367     public static function addToResaveList($bean)
368     {
369         if (!isset(self::$beansToResave[$bean->module_dir]))
370         {
371             self::$beansToResave[$bean->module_dir] = array();
372         }
373         self::$beansToResave[$bean->module_dir][$bean->id] = $bean;
374     }
375
376     /**
377      *
378      * @static
379      * @return void
380      */
381     public static function resaveRelatedBeans()
382     {
383         $GLOBALS['resavingRelatedBeans'] = true;
384
385         //Resave any bean not currently in the middle of a save operation
386         foreach(self::$beansToResave as $module => $beans)
387         {
388             foreach ($beans as $bean)
389             {
390                 if (empty($bean->deleted) && empty($bean->in_save))
391                 {
392                     $bean->save();
393                 }
394             }
395         }
396
397         $GLOBALS['resavingRelatedBeans'] = false;
398
399         //Reset the list of beans that will need to be resaved
400         self::$beansToResave = array();
401     }
402
403     /**
404      * @return bool true if the relationship is a flex / parent relationship
405      */
406     public function isParentRelationship()
407     {
408         //check role fields to see if this is a parent (flex relate) relationship
409         if(!empty($this->def["relationship_role_column"]) && !empty($this->def["relationship_role_column_value"])
410            && $this->def["relationship_role_column"] == "parent_type" && $this->def['rhs_key'] == "parent_id")
411         {
412             return true;
413         }
414         return false;
415     }
416
417     public function __get($name)
418     {
419         if (isset($this->def[$name]))
420             return $this->def[$name];
421
422         switch($name)
423         {
424             case "relationship_type":
425                 return $this->type;
426             case 'relationship_name':
427                 return $this->name;
428             case "lhs_module":
429                 return $this->getLHSModule();
430             case "rhs_module":
431                 return $this->getRHSModule();
432             case "lhs_table" :
433                 isset($this->def['lhs_table']) ? $this->def['lhs_table'] : "";
434             case "rhs_table" :
435                 isset($this->def['rhs_table']) ? $this->def['rhs_table'] : "";
436             case "list_fields":
437                 return array('lhs_table', 'lhs_key', 'rhs_module', 'rhs_table', 'rhs_key', 'relationship_type');
438         }
439
440         if (isset($this->$name))
441             return $this->$name;
442
443         return null;
444     }
445 }