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