]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - data/Link2.php
Release 6.3.0beta3
[Github/sugarcrm.git] / data / Link2.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
40 /*********************************************************************************
41
42 * Description:  Represents a relationship from a single beans perspective.
43 * Does not activly do work but is used by sugarbean to manipulate relationships.
44 * Work is defered to the relationship classes.
45  *
46 * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
47 * All Rights Reserved.
48 * Contributor(s): ______________________________________..
49 ********************************************************************************/
50 global $dictionary;
51 require_once("data/Relationships/RelationshipFactory.php");
52
53 class Link2 {
54
55         protected $relationship; //relationship object this link is tied to.
56         protected $focus;  //SugarBean this link uses as the context for its calls.
57     protected $def;  //Field def for this link
58     protected $name;  //Field name for this link
59     protected $beans;  //beans on the other side of the link
60     protected $rows;   //any additional fields on the relationship
61     protected $loaded; //true if this link has been loaded from the database
62     protected $relationship_fields = array();
63
64         /**
65      * @param  $linkName String name of a link field in the module's vardefs
66      * @param  $bean SugarBean focus bean for this link (one half of a relationship)
67      * @return void
68      *
69      */
70         function __construct($linkName, $bean, $linkDef = false){
71         $this->focus = $bean;
72         if (empty($bean->field_defs) || empty($bean->field_defs[$linkName]) || empty($bean->field_defs[$linkName]['relationship']))
73         {
74             if (empty($linkDef))
75             {
76                 //Assume $linkName is really relationship_name, and find the link name with the vardef manager
77                 $this->def = VardefManager::getLinkFieldForRelationship($bean->module_dir, $bean->object_name, $linkName);
78             }
79             else {
80                 $this->def = $linkDef;
81             }
82             if (is_array($this->def) && !isset($this->def['name']))
83             {
84                 //More than one link found, we need to figure out if we are currently on the LHS or RHS
85                 //assume lhs for now
86                 if (isset($this->def[0]['side']) && $this->def[0]['side'] == 'left')
87                 {
88                     $this->def = $this->def[0];
89                 }else if (isset($this->def[1]['side']) && $this->def[1]['side'] == 'left')
90                 {
91                     $this->def = $this->def[1];
92                 }
93                 else {
94                     $this->def = $this->def[0];
95                 }
96             }
97             if (empty($this->def['name']))
98             {
99                 $GLOBALS['log']->fatal("failed to find link for $linkName");
100                 return false;
101             }
102
103             $this->name = $this->def['name'];
104         } else {
105             $this->def = $bean->field_defs[$linkName];
106             $this->name = $linkName;
107         }
108         $this->relationship = SugarRelationshipFactory::getInstance()->getRelationship($this->def['relationship']);
109
110        if (!$this->loadedSuccesfully()) {
111            $GLOBALS['log']->fatal("{$this->name} for {$this->def['relationship']} failed to load\n");
112        }
113         //Following behavior is tied to a property(ignore_role) value in the vardef. It alters the values of 2 properties, ignore_role_filter and add_distinct.
114                 //the property values can be altered again before any requests are made.
115                 if (!empty($this->def) && is_array($this->def)) {
116                         if (array_key_exists('ignore_role', $this->def)) {
117                                 if ($this->def['ignore_role']) {
118                                         $this->ignore_role_filter=true;
119                                         $this->add_distinct=true;
120                                 }
121                         }
122                 }
123         }
124
125     public function loadedSuccesfully()
126     {
127         return !empty($this->relationship);
128     }
129
130     public function load()
131     {
132         $data = $this->relationship->load($this);
133         $this->rows = $data['rows'];
134         $this->beans = null;
135         $this->loaded = true;
136     }
137
138         /* This method will return the following based on cardinality of the relationship.
139          *  # one-to-many, many-to-many: empty array if not data is found else array of keys.
140          *  # if many-to-many and $role set to true : empty array if not data is found else
141          *  array of array which contain id+other fields.
142          *  # many-to-one, one-to-one: null if no linked data is found, else key value.
143          *
144          * For a self referencing relationship the function will behave as if the user is trying
145          * to access the child records. To get to the parent records use the getParent() method.
146          */
147     public function get($role = false) {
148         if (!$this->loaded)
149             $this->load();
150
151         return array_keys($this->rows);
152     }
153
154     /**
155      * @deprecated
156      *
157      * @return null
158      */
159         public function getRelatedTableName() {
160         return BeanFactory::getBean($this->getRelatedModuleName())->table_name;
161         }
162
163         public function getRelatedModuleName() {
164                 if (!$this->relationship) return false;
165
166         if ($this->getSide() == REL_LHS) {
167             return $this->relationship->getRHSModule();
168                 } else {
169             return $this->relationship->getLHSModule();
170                 }
171         }
172
173     public function getType()
174     {
175         switch ($this->relationship->type)
176         {
177             case REL_MANY_MANY:
178                 return "many";
179             case REL_ONE_ONE:
180                 return "one";
181             case REL_ONE_MANY:
182                 return $this->getSide() == REL_LHS ? "many" : "one";
183         }
184         return "many";
185     }
186
187     public function getFocus()
188     {
189         return $this->focus;
190     }
191
192
193
194     /**
195      * @deprecated
196      * @return
197      */
198         public function getRelatedFields(){
199                 return $this->relationship_fields;
200         }
201
202         public function getRelatedField($name){
203         if (!empty($this->relationship_fields) && !empty($this->relationship_fields[$name]))
204             return $this->relationship_fields[$name];
205         else
206             return null; //For now return null. Later try the relationship object directly.
207         }
208
209         public function getRelationshipObject() {
210            return $this->relationship;
211         }
212
213         public function getSide() {
214                 //First try the relationship
215         if ($this->relationship->getLHSLink() == $this->name &&
216             ($this->relationship->getLHSModule() == $this->focus->module_name)
217         ){
218             return REL_LHS;
219         }
220
221         if ($this->relationship->getRHSLink() == $this->name &&
222             ($this->relationship->getRHSModule() == $this->focus->module_name)
223         ){
224             return REL_RHS;
225         }
226
227         //Next try the vardef
228         if (!empty($this->def['side']))
229         {
230             if ((strtolower($this->def['side']) == 'left' || $this->def['side'] == REL_LHS)
231                 //Some relationships make have left in the vardef errorneously if generated by module builder
232                 && $this->name != $this->relationship->def['join_key_lhs'])
233             {
234                 return REL_LHS ;
235             }
236             else {
237                 return REL_RHS;
238             }
239         }
240
241         $GLOBALS['log']->error("Unable to get proper side for link {$this->name}");
242         }
243
244         protected function is_self_relationship() {
245                 return $this->relationship->isSelfReferencing();
246         }
247
248     public function isParentRelationship(){
249         return $this->relationship->isParentRelationship();
250     }
251
252         function getJoin($params, $return_array =false)
253         {
254         return $this->relationship->getJoin($this, $params, $return_array);
255         }
256
257         function getQuery($params = array())
258     {
259         return $this->relationship->getQuery($this, $params);
260         }
261
262     public function getSubpanelQuery($params = array(), $return_array = false)
263     {
264         return $this->relationship->getSubpanelQuery($this, $params, $return_array);
265     }
266
267         function getBeans() {
268         if (!$this->loaded) {
269             $this->load();
270         }
271         if(!is_array($this->beans))
272         {
273             $this->beans = array();
274             $rel_module = $this->getRelatedModuleName();
275             foreach ($this->rows as $id => $vals)
276             {
277                 $this->beans[$id] = BeanFactory::getBean($rel_module, $id);
278             }
279         }
280
281         return $this->beans;
282         }
283
284         /**
285      * use this function to create link between 2 objects
286          * 1:1 will be treated like 1 to many.
287          *
288      * todo handle self referencing relationships
289          *
290      * the function also allows for setting of values for additional field in the table being
291          * updated to save the relationship, in case of many-to-many relationships this would be the join table.
292      *
293      * @param array $rel_keys array of ids or SugarBean objects
294      * @param array $additional_values the values should be passed as key value pairs with column name as the key name and column value as key value.
295      *
296      * @return void
297      */
298     function add($rel_keys,$additional_values=array()) {
299         if (!is_array($rel_keys))
300             $rel_keys = array($rel_keys);
301
302         foreach($rel_keys as $key)
303         {
304             if (!($key instanceof SugarBean)) {
305                 $key = $this->getRelatedBean($key);
306                 if (!($key instanceof SugarBean)) {
307                     $GLOBALS['log']->error("Unable to load related bean by id");
308                     return false;
309                 }
310             }
311
312             if(empty($key->id) || empty($this->focus->id))
313                 return false;
314
315             if ($this->getSide() == REL_LHS) {
316                 $this->relationship->add($this->focus, $key, $additional_values);
317             }
318             else {
319                 $this->relationship->add($key, $this->focus, $additional_values);
320             }
321         }
322         }
323
324
325
326         /* this method operates on all related record, takes action based on cardinality of the relationship.
327          * one-to-one, one-to-many: update the rhs table's parent id with null
328          * many-to-one: update the lhs table's parent-id with null.
329          * many-to-many: delete rows from the link table. related table must have delted and date_modified column.
330          * if related_is is null, the methods assumes that the parent bean (whose id is passed) is being deleted.
331          * if both id and related_id are passed the metod unlinks a single relationship.
332          * parameters: id of the bean being deleted.
333          *
334          */
335         function delete($id, $related_id='') {
336         if (empty($this->focus->id))
337             $this->focus = BeanFactory::getBean($this->focus->module_name, $id);
338         if (!empty($related_id))
339         {
340             if (!($related_id instanceof SugarBean)) {
341                 $related_id = $this->getRelatedBean($related_id);
342             }
343             if ($this->getSide() == REL_LHS) {
344                 $this->relationship->remove($this->focus, $related_id);
345             }
346             else {
347                 $this->relationship->remove($related_id, $this->focus);
348             }
349         }
350         else
351         {
352             $this->relationship->removeAll($this);
353         }
354         }
355
356     protected function getRelatedBean($id = false)
357     {
358         return BeanFactory::getBean($this->getRelatedModuleName(), $id);
359     }
360
361         function relationship_exists($table_name, $join_key_values) {
362
363                 //find the key values for the table.
364                 $dup_keys=$this->_get_alternate_key_fields($table_name);
365                 if (empty($dup_keys)) {
366                         $GLOBALS['log']->debug("No alternate key define, skipping duplicate check..");
367                         return false;
368                 }
369
370                 $delimiter='';
371                 $this->_duplicate_where=' WHERE ';
372                 foreach ($dup_keys as $field) {
373                         //look for key in  $join_key_values, if found add to filter criteria else abort duplicate checking.
374                         if (isset($join_key_values[$field])) {
375
376                                 $this->_duplicate_where .= $delimiter.' '.$field."='".$join_key_values[$field]."'";
377                                 $delimiter='AND';
378                         } else {
379                                 $GLOBALS['log']->error('Duplicate checking aborted, Please supply a value for this column '.$field);
380                                 return false;
381                         }
382                 }
383                 //add deleted check.
384                 $this->_duplicate_where .= $delimiter.' deleted=0';
385
386                 $query='SELECT id FROM '.$table_name.$this->_duplicate_where;
387
388                 $GLOBALS['log']->debug("relationship_exists query(".$query.')');
389
390                 $result=$this->_db->query($query, true);
391                 $row = $this->_db->fetchByAssoc($result);
392
393                 if ($row == null) {
394                         return false;
395                 }
396                 else {
397                         $this->_duplicate_key=$row['id'];
398                         return true;
399                 }
400         }
401
402         /* returns array of keys for duplicate checking, first check for an index of type alternate_key, if not found searches for
403          * primary key.
404          *
405          */
406         protected function _get_alternate_key_fields($table_name) {
407                 $indices=Link::_get_link_table_definition($table_name,'indices');
408                 if (!empty($indices)) {
409                         foreach ($indices as $index) {
410                 if ( isset($index['type']) && $index['type'] == 'alternate_key' ) {
411                     return $index['fields'];
412                 }
413                         }
414                 }
415         //bug 32623, when the relationship is built in old version, there is no alternate_key. we have to use join_key_lhs and join_key_lhs.
416         $relDef = $this->relationship->def;
417         if (!empty($relDef['join_key_lhs']) && !empty($relDef['join_key_rhs']))
418                     return array($relDef['join_key_lhs'], $relDef['join_key_rhs']);
419         }
420
421         /*
422          */
423         protected function _get_link_table_definition($table_name, $def_name) {
424             global $dictionary;
425
426         if (isset($this->relationship->def[$def_name]))
427             return $this->relationship->def[$def_name];
428
429         return null;
430     }
431     /*
432      * Return the name of the role field for the passed many to many table.
433      * if there is no role filed : return false
434      */
435     protected function _get_link_table_role_field($table_name) {
436         $varDefs = $this->_get_link_table_definition($table_name, 'fields');
437         $role_field = false;
438         if(!empty($varDefs)){
439             $role_field = '';
440             foreach($varDefs as $v){
441                 if(strpos($v['name'], '_role') !== false){
442                     $role_field = $v['name'];
443                 }
444             }
445         }
446         return $role_field;
447     }
448
449     /**
450      * @deprecated
451      *
452      * @return boolean returns true if this link is LHS
453      */
454     public function _get_bean_position()
455     {
456         return $this->getSide() == REL_LHS;
457     }
458
459
460     public function &__get($name)
461     {
462         switch($name)
463         {
464             case "relationship_type":
465                 return $this->relationship->type;
466             case "_relationship":
467                 return $this->relationship;
468             case "beans":
469                 if (!is_array($this->beans))
470                     $this->getBeans();
471                 return $this->beans;
472         }
473         return $this->$name;
474     }
475
476     public function __set($name, $val)
477     {
478         if($name == "beans")
479             $this->beans = $val;
480
481     }
482
483     /**
484      * @param SugarBean $bean
485      * @return void
486      */
487     public function addBean($bean)
488     {
489         if (!is_array($this->beans))
490             $this->getBeans();
491         $this->beans[$bean->id] = $bean;
492     }
493
494     /**
495      * @param SugarBean $bean
496      * @return void
497      */
498     public function removeBean($bean)
499     {
500         if (!is_array($this->beans))
501             $this->getBeans();
502         unset($this->beans[$bean->id]);
503         unset($this->rows[$bean->id]);
504     }
505 }
506 ?>