]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - data/Link2.php
Release 6.3.0
[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      * @param  $linkDef Array Optional vardef for the link in case it can't be found in the passed in bean for the global dictionary
68      * @return void
69      *
70      */
71     function __construct($linkName, $bean, $linkDef = false){
72         $this->focus = $bean;
73         //Try to load the link vardef from the beans field defs. Otherwise start searching
74         if (empty($bean->field_defs) || empty($bean->field_defs[$linkName]) || empty($bean->field_defs[$linkName]['relationship']))
75         {
76             if (empty($linkDef))
77             {
78                 //Assume $linkName is really relationship_name, and find the link name with the vardef manager
79                 $this->def = VardefManager::getLinkFieldForRelationship($bean->module_dir, $bean->object_name, $linkName);
80             }
81             else {
82                 $this->def = $linkDef;
83             }
84             //Check if multiple links were found for a given relationship
85             if (is_array($this->def) && !isset($this->def['name']))
86             {
87                 //More than one link found, we need to figure out if we are currently on the LHS or RHS
88                 //default to lhs for now
89                 if (isset($this->def[0]['side']) && $this->def[0]['side'] == 'left')
90                 {
91                     $this->def = $this->def[0];
92                 }
93                 else if (isset($this->def[1]['side']) && $this->def[1]['side'] == 'left')
94                 {
95                     $this->def = $this->def[1];
96                 }
97                 else {
98                     $this->def = $this->def[0];
99                 }
100             }
101             if (empty($this->def['name']))
102             {
103                 $GLOBALS['log']->fatal("failed to find link for $linkName");
104                 return false;
105             }
106
107             $this->name = $this->def['name'];
108         }
109         else {
110             //Linkdef was found in the bean (this is the normal expectation)
111             $this->def = $bean->field_defs[$linkName];
112             $this->name = $linkName;
113         }
114         //Instantiate the relationship for this link.
115         $this->relationship = SugarRelationshipFactory::getInstance()->getRelationship($this->def['relationship']);
116
117         if (!$this->loadedSuccesfully())
118         {
119             $GLOBALS['log']->fatal("{$this->name} for {$this->def['relationship']} failed to load\n");
120         }
121         //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.
122         //the property values can be altered again before any requests are made.
123         if (!empty($this->def) && is_array($this->def)) {
124             if (array_key_exists('ignore_role', $this->def)) {
125                 if ($this->def['ignore_role']) {
126                     $this->ignore_role_filter=true;
127                     $this->add_distinct=true;
128                 }
129             }
130         }
131     }
132
133     /**
134      * Returns false if no relationship was found for this link
135      * @return bool
136      */
137     public function loadedSuccesfully()
138     {
139         return !empty($this->relationship);
140     }
141
142     /**
143      *  Forces the link to load the relationship rows.
144      * Will be called internally when the $rows property is accessed or get() is called
145      * @return void
146      */
147     public function load()
148     {
149         $data = $this->relationship->load($this);
150         $this->rows = $data['rows'];
151         $this->beans = null;
152         $this->loaded = true;
153     }
154
155     /**
156      * @return array ids of records related through this link
157      */
158     public function get($role = false) {
159         if (!$this->loaded)
160             $this->load();
161
162         return array_keys($this->rows);
163     }
164
165     /**
166      * @deprecated
167      *
168      * @return string name of table for the relationship of this link
169      */
170     public function getRelatedTableName() {
171         return BeanFactory::getBean($this->getRelatedModuleName())->table_name;
172     }
173
174     /**
175      * @return string the name of the module on the other side of this link
176      */
177     public function getRelatedModuleName() {
178         if (!$this->relationship) return false;
179
180         if ($this->getSide() == REL_LHS) {
181             return $this->relationship->getRHSModule();
182         } else {
183             return $this->relationship->getLHSModule();
184         }
185     }
186
187     /**
188      * @return string the name of the link field used on the other side of the rel
189      */
190     public function getRelatedModuleLinkName() {
191         if (!$this->relationship) return false;
192
193         if ($this->getSide() == REL_LHS) {
194             return $this->relationship->getRHSLink();
195         } else {
196             return $this->relationship->getLHSLink();
197         }
198     }
199
200     /**
201      *
202      * @return string "many" if multiple records can be related through this link
203      * or "one" if at most, one record can be related.
204      */
205     public function getType()
206     {
207         switch ($this->relationship->type)
208         {
209             case REL_MANY_MANY:
210                 return "many";
211             case REL_ONE_ONE:
212                 return "one";
213             case REL_ONE_MANY:
214                 return $this->getSide() == REL_LHS ? "many" : "one";
215         }
216         return "many";
217     }
218
219     /**
220      * @return SugarBean The parent Bean this link references
221      */
222     public function getFocus()
223     {
224         return $this->focus;
225     }
226
227     /**
228      * @deprecated
229      * @return list of fields that exist only on the relationship
230      */
231     public function getRelatedFields(){
232         return $this->relationship_fields;
233     }
234
235     /**
236      * @param $name
237      * @return The value for the relationship field $name
238      */
239     public function getRelatedField($name){
240         if (!empty($this->relationship_fields) && !empty($this->relationship_fields[$name]))
241             return $this->relationship_fields[$name];
242         else
243             return null; //For now return null. Later try the relationship object directly.
244     }
245
246     /**
247      * @return SugarRelationship the relationship object this link references
248      */
249     public function getRelationshipObject() {
250        return $this->relationship;
251     }
252
253     /**
254      * @return string "LHS" or "RHS" depending on the side of the relationship this link represents
255      */
256     public function getSide() {
257         //First try the relationship
258         if ($this->relationship->getLHSLink() == $this->name &&
259             ($this->relationship->getLHSModule() == $this->focus->module_name)
260         ){
261             return REL_LHS;
262         }
263
264         if ($this->relationship->getRHSLink() == $this->name &&
265             ($this->relationship->getRHSModule() == $this->focus->module_name)
266         ){
267             return REL_RHS;
268         }
269
270         //Next try the vardef
271         if (!empty($this->def['side']))
272         {
273             if ((strtolower($this->def['side']) == 'left' || $this->def['side'] == REL_LHS)
274                 //Some relationships make have left in the vardef errorneously if generated by module builder
275                 && $this->name != $this->relationship->def['join_key_lhs'])
276             {
277                 return REL_LHS ;
278             }
279             else {
280                 return REL_RHS;
281             }
282         }
283         //Next try using the id_name and relationship join keys
284         else if (!empty($this->def['id_name']))
285         {
286             if (isset($this->relationship->def['join_key_lhs']) && $this->def['id_name'] == $this->relationship->def['join_key_lhs'])
287                 return REL_RHS;
288             else if (isset($this->relationship->def['join_key_rhs']) && $this->def['id_name'] == $this->relationship->def['join_key_rhs'])
289                 return REL_LHS;
290         }
291
292         $GLOBALS['log']->error("Unable to get proper side for link {$this->name}");
293     }
294
295     /**
296      * @return bool true if LHSModule == RHSModule
297      */
298     protected function is_self_relationship() {
299         return $this->relationship->isSelfReferencing();
300     }
301
302     /**
303      * @return bool true if this link represents a relationship where the parent could be one of multiple modules. (ex. Activities parent)
304      */
305     public function isParentRelationship(){
306         return $this->relationship->isParentRelationship();
307     }
308
309     /**
310      * @param $params array of parameters. Possible parameters include:
311      * 'join_table_link_alias': alias the relationship join table in the query (for M2M relationships),
312      * 'join_table_alias': alias for the final table to be joined against (usually a module main table)
313      * @param bool $return_array if true the query is returned as a array broken up into
314      * select, join, where, type, rel_key, and joined_tables
315      * @return string/array join query for this link
316      */
317     function getJoin($params, $return_array =false)
318     {
319         return $this->relationship->getJoin($this, $params, $return_array);
320     }
321
322     /**
323      * @param array $params optional parameters. Possible Values;
324      * 'return_as_array': returns the query broken into 
325      * @return String/Array query to grab just ids for this relationship
326      */
327     function getQuery($params = array())
328     {
329         return $this->relationship->getQuery($this, $params);
330     }
331
332     /**
333      * This function is similair getJoin except for M2m relationships it won't join agaist the final table.
334      * Its used to retrieve the ID of the related beans only
335      * @param $params array of parameters. Possible parameters include:
336      * 'return_as_array': returns the query broken into
337      * @param bool $return_array same as passing 'return_as_array' into parameters
338      * @return string/array query to use when joining for subpanels
339      */
340     public function getSubpanelQuery($params = array(), $return_array = false)
341     {
342         if (!empty($this->def['ignore_role']))
343             $params['ignore_role'] = true;
344         return $this->relationship->getSubpanelQuery($this, $params, $return_array);
345     }
346
347     /**
348      * @return array of SugarBeans related through this link. Use with caution.
349      */
350     function getBeans() {
351         if (!$this->loaded) {
352             $this->load();
353         }
354         if(!is_array($this->beans))
355         {
356             $this->beans = array();
357             $rel_module = $this->getRelatedModuleName();
358             foreach ($this->rows as $id => $vals)
359             {
360                 $tmpBean = BeanFactory::getBean($rel_module, $id);
361                 if($tmpBean !== FALSE)
362                     $this->beans[$id] = $tmpBean;
363             }
364         }
365
366         return $this->beans;
367     }
368
369     /**
370      * @return bool true if this link has initialized its related beans.
371      */
372     public function beansAreLoaded() {
373         return is_array($this->beans);
374     }
375
376     /**
377      * use this function to create link between 2 objects
378      * 1:1 will be treated like 1 to many.
379      *
380      * the function also allows for setting of values for additional field in the table being
381      * updated to save the relationship, in case of many-to-many relationships this would be the join table.
382      *
383      * @param array $rel_keys array of ids or SugarBean objects. If you have the bean in memory, pass it in.
384      * @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.
385      *
386      * @return void
387      */
388     function add($rel_keys,$additional_values=array()) {
389         if (!is_array($rel_keys))
390             $rel_keys = array($rel_keys);
391
392         foreach($rel_keys as $key)
393         {
394             //We must use beans for LogicHooks and other buisiness logic to fire correctly
395             if (!($key instanceof SugarBean)) {
396                 $key = $this->getRelatedBean($key);
397                 if (!($key instanceof SugarBean)) {
398                     $GLOBALS['log']->error("Unable to load related bean by id");
399                     return false;
400                 }
401             }
402
403             if(empty($key->id) || empty($this->focus->id))
404                 return false;
405
406             if ($this->getSide() == REL_LHS) {
407                 $this->relationship->add($this->focus, $key, $additional_values);
408             }
409             else {
410                 $this->relationship->add($key, $this->focus, $additional_values);
411             }
412         }
413     }
414
415
416
417     /**
418      * Marks the relationship delted for this given record pair.
419      * @param $id id of the Parent/Focus SugarBean
420      * @param string $related_id id or SugarBean to unrelate. Pass a SugarBean if you have it.
421      * @return void
422      */
423     function delete($id, $related_id='') {
424         if (empty($this->focus->id))
425             $this->focus = BeanFactory::getBean($this->focus->module_name, $id);
426         if (!empty($related_id))
427         {
428             if (!($related_id instanceof SugarBean)) {
429                 $related_id = $this->getRelatedBean($related_id);
430             }
431             if ($this->getSide() == REL_LHS) {
432                 $this->relationship->remove($this->focus, $related_id);
433             }
434             else {
435                 $this->relationship->remove($related_id, $this->focus);
436             }
437         }
438         else
439         {
440             $this->relationship->removeAll($this);
441         }
442     }
443
444     /**
445      * Returns a SugarBean with the given ID from the related module.
446      * @param bool $id id of related record to retrieve
447      * @return SugarBean
448      */
449     protected function getRelatedBean($id = false)
450     {
451         return BeanFactory::getBean($this->getRelatedModuleName(), $id);
452     }
453
454     public function &__get($name)
455     {
456         switch($name)
457         {
458             case "relationship_type":
459                 return $this->relationship->type;
460             case "_relationship":
461                 return $this->relationship;
462             case "beans":
463                 if (!is_array($this->beans))
464                     $this->getBeans();
465                 return $this->beans;
466             case "rows":
467                 if (!is_array($this->rows))
468                     $this->load();
469                 return $this->rows;
470         }
471         return $this->$name;
472     }
473
474     public function __set($name, $val)
475     {
476         if($name == "beans")
477             $this->beans = $val;
478
479     }
480
481     /**
482      * @param SugarBean $bean
483      * @return void
484      */
485     public function addBean($bean)
486     {
487         if (!is_array($this->beans))
488             $this->getBeans();
489         $this->beans[$bean->id] = $bean;
490     }
491
492     /**
493      * @param SugarBean $bean
494      * @return void
495      */
496     public function removeBean($bean)
497     {
498         if (!is_array($this->beans))
499             $this->getBeans();
500         unset($this->beans[$bean->id]);
501         unset($this->rows[$bean->id]);
502     }
503
504
505     /**
506      * @param $table_name string relationship table
507      * @param $join_key_values array of key=>values to identify this relationship by
508      * @return bool true if the given join key set exists in the relationship table
509      */
510     public function relationship_exists($table_name, $join_key_values) {
511
512         //find the key values for the table.
513         $dup_keys=$this->_get_alternate_key_fields($table_name);
514         if (empty($dup_keys)) {
515             $GLOBALS['log']->debug("No alternate key define, skipping duplicate check..");
516             return false;
517         }
518
519         $delimiter='';
520         $this->_duplicate_where=' WHERE ';
521         foreach ($dup_keys as $field) {
522             //look for key in  $join_key_values, if found add to filter criteria else abort duplicate checking.
523             if (isset($join_key_values[$field])) {
524
525                 $this->_duplicate_where .= $delimiter.' '.$field."='".$join_key_values[$field]."'";
526                 $delimiter='AND';
527             } else {
528                 $GLOBALS['log']->error('Duplicate checking aborted, Please supply a value for this column '.$field);
529                 return false;
530             }
531         }
532         //add deleted check.
533         $this->_duplicate_where .= $delimiter.' deleted=0';
534
535         $query='SELECT id FROM '.$table_name.$this->_duplicate_where;
536
537         $GLOBALS['log']->debug("relationship_exists query(".$query.')');
538
539         $result=$this->_db->query($query, true);
540         $row = $this->_db->fetchByAssoc($result);
541
542         if ($row == null) {
543             return false;
544         }
545         else {
546             $this->_duplicate_key=$row['id'];
547             return true;
548         }
549     }
550
551     //Below are functions not used directly and exist for backwards compatiblity with customizations, will be removed in a later version
552
553     /* returns array of keys for duplicate checking, first check for an index of type alternate_key, if not found searches for
554      * primary key.
555      *
556      */
557     public function _get_alternate_key_fields($table_name) {
558         $indices=Link::_get_link_table_definition($table_name,'indices');
559         if (!empty($indices)) {
560             foreach ($indices as $index) {
561                 if ( isset($index['type']) && $index['type'] == 'alternate_key' ) {
562                     return $index['fields'];
563                 }
564             }
565         }
566         //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.
567         $relDef = $this->relationship->def;
568         if (!empty($relDef['join_key_lhs']) && !empty($relDef['join_key_rhs']))
569             return array($relDef['join_key_lhs'], $relDef['join_key_rhs']);
570     }
571
572     /**
573      * @depricated
574      * Gets the vardef for the relationship of this link.
575      */
576     public function _get_link_table_definition($table_name, $def_name) {
577
578         if (isset($this->relationship->def[$def_name]))
579             return $this->relationship->def[$def_name];
580
581         return null;
582     }
583
584     /**
585      * @depricated
586      * Return the name of the role field for the passed many to many table.
587      * if there is no role filed : return false
588      * @param $table_name name of relationship table to inspect
589      * @return bool|string
590      */
591     public function _get_link_table_role_field($table_name) {
592         $varDefs = $this->_get_link_table_definition($table_name, 'fields');
593         $role_field = false;
594         if(!empty($varDefs)){
595             $role_field = '';
596             foreach($varDefs as $v){
597                 if(strpos($v['name'], '_role') !== false){
598                     $role_field = $v['name'];
599                 }
600             }
601         }
602         return $role_field;
603     }
604
605     /**
606      * @deprecated
607      *
608      * @return boolean returns true if this link is LHS
609      */
610     public function _get_bean_position()
611     {
612         return $this->getSide() == REL_LHS;
613     }
614 }
615 ?>