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