]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - data/Relationships/SugarRelationship.php
Release 6.5.5
[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-2012 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 boolean
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, $params = array());
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 boolean      true if it was successful, false if it was not
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                 return $this->remove($focus, $relBean);
132             else
133                 return $this->remove($relBean, $focus);
134         }
135
136         return false;
137     }
138
139     /**
140      * @param $rowID id of SugarBean to remove from the relationship
141      * @return void
142      */
143     public function removeById($rowID){
144         $this->removeRow(array("id" => $rowID));
145     }
146
147     /**
148      * @return string name of right hand side module.
149      */
150     public function getRHSModule()
151     {
152         return $this->def['rhs_module'];
153     }
154
155     /**
156      * @return string name of left hand side module.
157      */
158     public function getLHSModule()
159     {
160         return $this->def['lhs_module'];
161     }
162
163     /**
164      * @return String left link in relationship.
165      */
166     public function getLHSLink()
167     {
168         return $this->lhsLink;
169     }
170
171     /**
172      * @return String right link in relationship.
173      */
174     public function getRHSLink()
175     {
176         return $this->rhsLink;
177     }
178
179     /**
180      * @return array names of fields stored on the relationship
181      */
182     public function getFields()
183     {
184         return isset($this->def['fields']) ? $this->def['fields'] : array();
185     }
186
187     /**
188      * @param array $row values to be inserted into the relationship
189      * @return bool|void null if new row was inserted and true if an existing row was updated
190      */
191     protected function addRow($row)
192     {
193         $existing = $this->checkExisting($row);
194         if (!empty($existing)) //Update the existing row, overriding the values with those passed in
195             return $this->updateRow($existing['id'], array_merge($existing, $row));
196
197         $values = array();
198         foreach($this->getFields() as  $def)
199         {
200             $field = $def['name'];
201             if (isset($row[$field]))
202             {
203                 $values[$field] = "'{$row[$field]}'";
204             }
205         }
206         $columns = implode(',', array_keys($values));
207         $values = implode(',', $values);
208         if (!empty($values))
209         {
210             $query = "INSERT INTO {$this->getRelationshipTable()} ($columns) VALUES ($values)";
211             DBManagerFactory::getInstance()->query($query);
212         }
213     }
214
215     /**
216      * @param $id id of row to update
217      * @param $values values to insert into row
218      * @return resource result of update satatement
219      */
220     protected function updateRow($id, $values)
221     {
222         $newVals = array();
223         //Unset the ID since we are using it to update the row
224         if (isset($values['id'])) unset($values['id']);
225         foreach($values as $field => $val)
226         {
227             $newVals[] = "$field='$val'";
228         }
229
230         $newVals = implode(",",$newVals);
231
232         $query = "UPDATE {$this->getRelationshipTable()} set $newVals WHERE id='$id'";
233
234         return DBManagerFactory::getInstance()->query($query);
235     }
236
237     /**
238      * Removes one or more rows from the relationship table
239      * @param $where array of field=>value pairs to match
240      * @return bool|resource
241      */
242     protected function removeRow($where)
243     {
244         if (empty($where))
245             return false;
246
247         $date_modified = TimeDate::getInstance()->getNow()->asDb();
248         $stringSets = array();
249         foreach ($where as $field => $val)
250         {
251             $stringSets[] = "$field = '$val'";
252         }
253         $whereString = "WHERE " . implode(" AND ", $stringSets);
254
255         $query = "UPDATE {$this->getRelationshipTable()} set deleted=1 , date_modified = '$date_modified' $whereString";
256
257         return DBManagerFactory::getInstance()->query($query);
258
259     }
260
261     /**
262      * Checks for an existing row who's keys match the one passed in.
263      * @param  $row
264      * @return array|bool returns false if now row is found, otherwise the row is returned
265      */
266     protected function checkExisting($row)
267     {
268         $leftIDName = $this->def['join_key_lhs'];
269         $rightIDName = $this->def['join_key_rhs'];
270         if (empty($row[$leftIDName]) ||  empty($row[$rightIDName]))
271             return false;
272
273         $leftID = $row[$leftIDName];
274         $rightID = $row[$rightIDName];
275         //Check the relationship role as well
276         $roleCheck = $this->getRoleWhere();
277
278         $query = "SELECT * FROM {$this->getRelationshipTable()} WHERE $leftIDName='$leftID' AND $rightIDName='$rightID' $roleCheck AND deleted=0";
279
280         $db = DBManagerFactory::getInstance();
281         $result = $db->query($query);
282         $row = $db->fetchByAssoc($result);
283         if (!empty($row))
284         {
285             return $row;
286         } else{
287             return false;
288         }
289     }
290
291     /**
292      * Gets the relationship role column check for the where clause
293      * @param string $table
294      * @return string
295      */
296     protected function getRoleWhere($table = "", $ignore_role_filter = false)
297     {
298         $ignore_role_filter = $ignore_role_filter || $this->ignore_role_filter;
299         $roleCheck = "";
300         if (empty ($table))
301             $table = $this->getRelationshipTable();
302         if (!empty($this->def['relationship_role_column']) && !empty($this->def["relationship_role_column_value"]) && !$ignore_role_filter )
303         {
304             if (empty($table))
305                 $roleCheck = " AND $this->relationship_role_column";
306             else
307                 $roleCheck = " AND $table.{$this->relationship_role_column}";
308             //role column value.
309             if (empty($this->def['relationship_role_column_value']))
310             {
311                 $roleCheck.=' IS NULL';
312             } else {
313                 $roleCheck.= " = '$this->relationship_role_column_value'";
314             }
315         }
316         return $roleCheck;
317     }
318
319     /**
320      * @param SugarBean $focus base bean the hooks is triggered from
321      * @param SugarBean $related bean being added/removed/updated from relationship
322      * @param string $link_name name of link being triggerd
323      * @return array base arguments to pass to relationship logic hooks
324      */
325     protected function getCustomLogicArguments($focus, $related, $link_name)
326     {
327         $custom_logic_arguments = array();
328         $custom_logic_arguments['id'] = $focus->id;
329         $custom_logic_arguments['related_id'] = $related->id;
330         $custom_logic_arguments['module'] = $focus->module_dir;
331         $custom_logic_arguments['related_module'] = $related->module_dir;
332         $custom_logic_arguments['link'] = $link_name;
333         $custom_logic_arguments['relationship'] = $this->name;
334
335         return $custom_logic_arguments;
336     }
337
338     /**
339      * Call the before add logic hook for a given link
340      * @param  SugarBean $focus base bean the hooks is triggered from
341      * @param  SugarBean $related bean being added/removed/updated from relationship
342      * @param string $link_name name of link being triggerd
343      * @return void
344      */
345     protected function callBeforeAdd($focus, $related, $link_name="")
346     {
347         $custom_logic_arguments = $this->getCustomLogicArguments($focus, $related, $link_name);
348         $focus->call_custom_logic('before_relationship_add', $custom_logic_arguments);
349     }
350
351     /**
352      * Call the after add logic hook for a given link
353      * @param  SugarBean $focus base bean the hooks is triggered from
354      * @param  SugarBean $related bean being added/removed/updated from relationship
355      * @param string $link_name name of link being triggerd
356      * @return void
357      */
358     protected function callAfterAdd($focus, $related, $link_name="")
359     {
360         $custom_logic_arguments = $this->getCustomLogicArguments($focus, $related, $link_name);
361         $focus->call_custom_logic('after_relationship_add', $custom_logic_arguments);
362     }
363
364     /**
365      * @param  SugarBean $focus
366      * @param  SugarBean $related
367      * @param string $link_name
368      * @return void
369      */
370     protected function callBeforeDelete($focus, $related, $link_name="")
371     {
372         $custom_logic_arguments = $this->getCustomLogicArguments($focus, $related, $link_name);
373         $focus->call_custom_logic('before_relationship_delete', $custom_logic_arguments);
374     }
375
376     /**
377      * @param  SugarBean $focus
378      * @param  SugarBean $related
379      * @param string $link_name
380      * @return void
381      */
382     protected function callAfterDelete($focus, $related, $link_name="")
383     {
384         $custom_logic_arguments = $this->getCustomLogicArguments($focus, $related, $link_name);
385         $focus->call_custom_logic('after_relationship_delete', $custom_logic_arguments);
386     }
387
388     /**
389      * @param $optional_array clause to add to the where query when populating this relationship. It should be in the
390      * @param string $add_and
391      * @param string $prefix
392      * @return string
393      */
394     protected function getOptionalWhereClause($optional_array) {
395         //lhs_field, operator, and rhs_value must be set in optional_array
396         foreach(array("lhs_field", "operator", "rhs_value") as $required){
397             if (empty($optional_array[$required]))
398                 return "";
399         }
400
401         return $optional_array['lhs_field']."".$optional_array['operator']."'".$optional_array['rhs_value']."'";
402     }
403
404     /**
405      * Adds a realted Bean to the list to be resaved along with the current bean.
406      * @static
407      * @param  SugarBean $bean
408      * @return void
409      */
410     public static function addToResaveList($bean)
411     {
412         if (!isset(self::$beansToResave[$bean->module_dir]))
413         {
414             self::$beansToResave[$bean->module_dir] = array();
415         }
416         self::$beansToResave[$bean->module_dir][$bean->id] = $bean;
417     }
418
419     /**
420      *
421      * @static
422      * @return void
423      */
424     public static function resaveRelatedBeans()
425     {
426         $GLOBALS['resavingRelatedBeans'] = true;
427
428         //Resave any bean not currently in the middle of a save operation
429         foreach(self::$beansToResave as $module => $beans)
430         {
431             foreach ($beans as $bean)
432             {
433                 if (empty($bean->deleted) && empty($bean->in_save))
434                 {
435                     $bean->save();
436                 }
437             }
438         }
439
440         $GLOBALS['resavingRelatedBeans'] = false;
441
442         //Reset the list of beans that will need to be resaved
443         self::$beansToResave = array();
444     }
445
446     /**
447      * @return bool true if the relationship is a flex / parent relationship
448      */
449     public function isParentRelationship()
450     {
451         //check role fields to see if this is a parent (flex relate) relationship
452         if(!empty($this->def["relationship_role_column"]) && !empty($this->def["relationship_role_column_value"])
453            && $this->def["relationship_role_column"] == "parent_type" && $this->def['rhs_key'] == "parent_id")
454         {
455             return true;
456         }
457         return false;
458     }
459
460     public function __get($name)
461     {
462         if (isset($this->def[$name]))
463             return $this->def[$name];
464
465         switch($name)
466         {
467             case "relationship_type":
468                 return $this->type;
469             case 'relationship_name':
470                 return $this->name;
471             case "lhs_module":
472                 return $this->getLHSModule();
473             case "rhs_module":
474                 return $this->getRHSModule();
475             case "lhs_table" :
476                 isset($this->def['lhs_table']) ? $this->def['lhs_table'] : "";
477             case "rhs_table" :
478                 isset($this->def['rhs_table']) ? $this->def['rhs_table'] : "";
479             case "list_fields":
480                 return array('lhs_table', 'lhs_key', 'rhs_module', 'rhs_table', 'rhs_key', 'relationship_type');
481         }
482
483         if (isset($this->$name))
484             return $this->$name;
485
486         return null;
487     }
488 }