]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - data/Link2.php
Release 6.5.3
[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 bean's perspective.
43 * Does not actively do work but is used by sugarbean to manipulate relationships.
44 * Work is deferred 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 ( isset($this->def['ignore_role']) ) {
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($params = array())
157     {
158         $data = $this->query($params);
159         $this->rows = $data['rows'];
160         $this->beans = null;
161         $this->loaded = true;
162     }
163
164     /**
165      *  Perform a query on this relationship.
166      *
167      * @param array $params An array that can contain the following parameters:<br/>
168      * <ul><li><b>where:</b> An array with 3 key/value pairs.
169      *  lhs_field: The name of the field to check search.
170      *  operator: The operator to use in the search.
171      *  rhs_value: The value to search for.<br/>
172      *  Example:<pre>
173      *  'where' => array(
174              'lhs_field' => 'source',
175              'operator' => '=',
176              'rhs_value' => 'external'
177          )</pre>
178      *  </li>
179      * <li><b>limit:</b> The maximum number of rows</li>
180      * <li><b>deleted:</b> If deleted is set to 1, only deleted records related to the current record will be returned.</li></ul>
181      */
182     public function query($params){
183         return $this->relationship->load($this, $params);
184     }
185
186     /**
187      * @return array ids of records related through this link
188      */
189     public function get($role = false) {
190         if (!$this->loaded)
191             $this->load();
192
193         return array_keys($this->rows);
194     }
195
196     /**
197      * @deprecated
198      *
199      * @return string name of table for the relationship of this link
200      */
201     public function getRelatedTableName() {
202         return BeanFactory::getBean($this->getRelatedModuleName())->table_name;
203     }
204
205     /**
206      * @return string the name of the module on the other side of this link
207      */
208     public function getRelatedModuleName() {
209         if (!$this->relationship) return false;
210
211         if ($this->getSide() == REL_LHS) {
212             return $this->relationship->getRHSModule();
213         } else {
214             return $this->relationship->getLHSModule();
215         }
216     }
217
218     /**
219      * @return string the name of the link field used on the other side of the rel
220      */
221     public function getRelatedModuleLinkName() {
222         if (!$this->relationship) return false;
223
224         if ($this->getSide() == REL_LHS) {
225             return $this->relationship->getRHSLink();
226         } else {
227             return $this->relationship->getLHSLink();
228         }
229     }
230
231     /**
232      *
233      * @return string "many" if multiple records can be related through this link
234      * or "one" if at most, one record can be related.
235      */
236     public function getType()
237     {
238         switch ($this->relationship->type)
239         {
240             case REL_MANY_MANY:
241                 return "many";
242             case REL_ONE_ONE:
243                 return "one";
244             case REL_ONE_MANY:
245                 return $this->getSide() == REL_LHS ? "many" : "one";
246         }
247         return "many";
248     }
249
250     /**
251      * @return SugarBean The parent Bean this link references
252      */
253     public function getFocus()
254     {
255         return $this->focus;
256     }
257
258     /**
259      * @deprecated
260      * @return list of fields that exist only on the relationship
261      */
262     public function getRelatedFields(){
263         return $this->relationship_fields;
264     }
265
266     /**
267      * @param $name
268      * @return The value for the relationship field $name
269      */
270     public function getRelatedField($name){
271         if (!empty($this->relationship_fields) && !empty($this->relationship_fields[$name]))
272             return $this->relationship_fields[$name];
273         else
274             return null; //For now return null. Later try the relationship object directly.
275     }
276
277     /**
278      * @return SugarRelationship the relationship object this link references
279      */
280     public function getRelationshipObject() {
281        return $this->relationship;
282     }
283
284     /**
285      * @return string "LHS" or "RHS" depending on the side of the relationship this link represents
286      */
287     public function getSide() {
288         //First try the relationship
289         if ($this->relationship->getLHSLink() == $this->name &&
290             ($this->relationship->getLHSModule() == $this->focus->module_name)
291         ){
292             return REL_LHS;
293         }
294
295         if ($this->relationship->getRHSLink() == $this->name &&
296             ($this->relationship->getRHSModule() == $this->focus->module_name)
297         ){
298             return REL_RHS;
299         }
300
301         //Next try the vardef
302         if (!empty($this->def['side']))
303         {
304             if ((strtolower($this->def['side']) == 'left' || $this->def['side'] == REL_LHS)
305                 //Some relationships make have left in the vardef erroneously if generated by module builder
306                 && $this->name != $this->relationship->def['join_key_lhs'])
307             {
308                 return REL_LHS ;
309             }
310             else {
311                 return REL_RHS;
312             }
313         }
314         //Next try using the id_name and relationship join keys
315         else if (!empty($this->def['id_name']))
316         {
317             if (isset($this->relationship->def['join_key_lhs']) && $this->def['id_name'] == $this->relationship->def['join_key_lhs'])
318                 return REL_RHS;
319             else if (isset($this->relationship->def['join_key_rhs']) && $this->def['id_name'] == $this->relationship->def['join_key_rhs'])
320                 return REL_LHS;
321         }
322
323         $GLOBALS['log']->error("Unable to get proper side for link {$this->name}");
324     }
325
326     /**
327      * @return bool true if LHSModule == RHSModule
328      */
329     protected function is_self_relationship() {
330         return $this->relationship->isSelfReferencing();
331     }
332
333     /**
334      * @return bool true if this link represents a relationship where the parent could be one of multiple modules. (ex. Activities parent)
335      */
336     public function isParentRelationship(){
337         return $this->relationship->isParentRelationship();
338     }
339
340     /**
341      * @param $params array of parameters. Possible parameters include:
342      * 'join_table_link_alias': alias the relationship join table in the query (for M2M relationships),
343      * 'join_table_alias': alias for the final table to be joined against (usually a module main table)
344      * @param bool $return_array if true the query is returned as a array broken up into
345      * select, join, where, type, rel_key, and joined_tables
346      * @return string/array join query for this link
347      */
348     function getJoin($params, $return_array =false)
349     {
350         return $this->relationship->getJoin($this, $params, $return_array);
351     }
352
353     /**
354      * @param array $params optional parameters. Possible Values;
355      * 'return_as_array': returns the query broken into
356      * @return String/Array query to grab just ids for this relationship
357      */
358     function getQuery($params = array())
359     {
360         return $this->relationship->getQuery($this, $params);
361     }
362
363     /**
364      * This function is similair getJoin except for M2m relationships it won't join agaist the final table.
365      * Its used to retrieve the ID of the related beans only
366      * @param $params array of parameters. Possible parameters include:
367      * 'return_as_array': returns the query broken into
368      * @param bool $return_array same as passing 'return_as_array' into parameters
369      * @return string/array query to use when joining for subpanels
370      */
371     public function getSubpanelQuery($params = array(), $return_array = false)
372     {
373         if (!empty($this->def['ignore_role']))
374             $params['ignore_role'] = true;
375         return $this->relationship->getSubpanelQuery($this, $params, $return_array);
376     }
377
378     /**
379      * Use with caution as if you have a large list of beans in the relationship,
380      * it can cause the app to timeout or run out of memory.
381      *
382      * @param array $params An array that can contain the following parameters:<br/>
383      * <ul><li><b>where:</b> An array with 3 key/value pairs.
384      *  lhs_field: The name of the field to check search.
385      *  operator: The operator to use in the search.
386      *  rhs_value: The value to search for.<br/>
387      *  Example:<pre>
388      *  'where' => array(
389              'lhs_field' => 'source',
390              'operator' => '=',
391              'rhs_value' => 'external'
392          )</pre>
393      *  </li>
394      * <li><b>limit:</b> The maximum number of beans to load.</li>
395      * <li><b>deleted:</b> If deleted is set to 1, only deleted records related to the current record will be returned.</li></ul>
396      * @return array of SugarBeans related through this link.
397      */
398     function getBeans($params = array()) {
399         //Some depricated code attempts to pass in the old format to getBeans with a large number of useless paramters.
400         //reset the parameters if they are not in the new array format.
401         if (!is_array($params))
402             $params = array();
403
404         if (!$this->loaded && empty($params)) {
405             $this->load();
406         }
407
408         $rows = $this->rows;
409         //If params is set, we are doing a query rather than a complete load of the relationship
410         if (!empty($params)) {
411             $data = $this->query($params);
412             $rows = $data['rows'];
413         }
414
415         $result = array();
416         if(!$this->beansAreLoaded() || !empty($params))
417         {
418             if (!is_array($this->beans))
419                 $this->beans = array();
420
421             $rel_module = $this->getRelatedModuleName();
422
423             //First swap in the temp loaded beans, only if we are doing a complete load (no params)
424             if (empty($params)) {
425                 $result = $this->tempBeans;
426                 $this->tempBeans = array();
427             }
428
429             //now load from the rows
430             foreach ($rows as $id => $vals)
431             {
432                 if (empty($this->beans[$id]))
433                 {
434                     $tmpBean = BeanFactory::getBean($rel_module, $id);
435                     if($tmpBean !== FALSE)
436                         $result[$id] = $tmpBean;
437                 } else {
438                     $result[$id] = $this->beans[$id];
439                 }
440             }
441
442             //If we did a compelte load, cache the result in $this->beans
443             if (empty($params))
444                 $this->beans = $result;
445         } else {
446             $result = $this->beans;
447         }
448
449         return $result;
450     }
451
452     /**
453      * @return bool true if this link has initialized its related beans.
454      */
455     public function beansAreLoaded() {
456         return is_array($this->beans);
457     }
458
459     /**
460      * use this function to create link between 2 objects
461      * 1:1 will be treated like 1 to many.
462      *
463      * the function also allows for setting of values for additional field in the table being
464      * updated to save the relationship, in case of many-to-many relationships this would be the join table.
465      *
466      * @param array $rel_keys array of ids or SugarBean objects. If you have the bean in memory, pass it in.
467      * @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.
468      *
469      * @return void
470      */
471     function add($rel_keys,$additional_values=array()) {
472         if (!is_array($rel_keys))
473             $rel_keys = array($rel_keys);
474
475         foreach($rel_keys as $key)
476         {
477             //We must use beans for LogicHooks and other business logic to fire correctly
478             if (!($key instanceof SugarBean)) {
479                 $key = $this->getRelatedBean($key);
480                 if (!($key instanceof SugarBean)) {
481                     $GLOBALS['log']->error("Unable to load related bean by id");
482                     return false;
483                 }
484             }
485
486             if(empty($key->id) || empty($this->focus->id))
487                 return false;
488
489             if ($this->getSide() == REL_LHS) {
490                 $this->relationship->add($this->focus, $key, $additional_values);
491             }
492             else {
493                 $this->relationship->add($key, $this->focus, $additional_values);
494             }
495         }
496     }
497
498
499
500     /**
501      * Marks the relationship delted for this given record pair.
502      * @param $id id of the Parent/Focus SugarBean
503      * @param string $related_id id or SugarBean to unrelate. Pass a SugarBean if you have it.
504      * @return void
505      */
506     function delete($id, $related_id='') {
507         if (empty($this->focus->id))
508             $this->focus = BeanFactory::getBean($this->focus->module_name, $id);
509         if (!empty($related_id))
510         {
511             if (!($related_id instanceof SugarBean)) {
512                 $related_id = $this->getRelatedBean($related_id);
513             }
514             if ($this->getSide() == REL_LHS) {
515                 $this->relationship->remove($this->focus, $related_id);
516             }
517             else {
518                 $this->relationship->remove($related_id, $this->focus);
519             }
520         }
521         else
522         {
523             $this->relationship->removeAll($this);
524         }
525     }
526
527     /**
528      * Returns a SugarBean with the given ID from the related module.
529      * @param bool $id id of related record to retrieve
530      * @return SugarBean
531      */
532     protected function getRelatedBean($id = false)
533     {
534         $params = array('encode' => true, 'deleted' => true);
535         return BeanFactory::getBean($this->getRelatedModuleName(), $id, $params);
536     }
537
538     public function &__get($name)
539     {
540         switch($name)
541         {
542             case "relationship_type":
543                 return $this->relationship->type;
544             case "_relationship":
545                 return $this->relationship;
546             case "beans":
547                 if (!is_array($this->beans))
548                     $this->getBeans();
549                 return $this->beans;
550             case "rows":
551                 if (!is_array($this->rows))
552                     $this->load();
553                 return $this->rows;
554         }
555         return $this->$name;
556     }
557
558     public function __set($name, $val)
559     {
560         if($name == "beans")
561             $this->beans = $val;
562
563     }
564
565     /**
566      * Add a bean object to the list of beans currently loaded to this relationship.
567      * This for the most part should not need to be called except by the relatipnship implementation classes.
568      * @param SugarBean $bean
569      * @return void
570      */
571     public function addBean($bean)
572     {
573         if (!is_array($this->beans))
574         {
575             $this->tempBeans[$bean->id] = $bean;
576         }
577         else {
578             $this->beans[$bean->id] = $bean;
579         }
580
581     }
582
583     /**
584      * Remove a bean object from the list of beans currently loaded to this relationship.
585      * This for the most part should not need to be called except by the relatipnship implementation classes.
586      *
587      * @param SugarBean $bean
588      * @return void
589      */
590     public function removeBean($bean)
591     {
592         if (!is_array($this->beans) && isset($this->tempBeans[$bean->id]))
593         {
594             unset($this->tempBeans[$bean->id]);
595         } else {
596             unset($this->beans[$bean->id]);
597             unset($this->rows[$bean->id]);
598         }
599     }
600
601
602     /**
603      * Directly queries the databse for set of values. The relationship classes and not link should handle this.
604      * @deprecated
605      * @param $table_name string relationship table
606      * @param $join_key_values array of key=>values to identify this relationship by
607      * @return bool true if the given join key set exists in the relationship table
608      */
609     public function relationship_exists($table_name, $join_key_values) {
610
611         //find the key values for the table.
612         $dup_keys=$this->_get_alternate_key_fields($table_name);
613         if (empty($dup_keys)) {
614             $GLOBALS['log']->debug("No alternate key define, skipping duplicate check..");
615             return false;
616         }
617
618         $delimiter='';
619         $this->_duplicate_where=' WHERE ';
620         foreach ($dup_keys as $field) {
621             //look for key in  $join_key_values, if found add to filter criteria else abort duplicate checking.
622             if (isset($join_key_values[$field])) {
623
624                 $this->_duplicate_where .= $delimiter.' '.$field."='".$join_key_values[$field]."'";
625                 $delimiter='AND';
626             } else {
627                 $GLOBALS['log']->error('Duplicate checking aborted, Please supply a value for this column '.$field);
628                 return false;
629             }
630         }
631         //add deleted check.
632         $this->_duplicate_where .= $delimiter.' deleted=0';
633
634         $query='SELECT id FROM '.$table_name.$this->_duplicate_where;
635
636         $GLOBALS['log']->debug("relationship_exists query(".$query.')');
637
638         $result=$this->_db->query($query, true);
639         $row = $this->_db->fetchByAssoc($result);
640
641         if ($row == null) {
642             return false;
643         }
644         else {
645             $this->_duplicate_key=$row['id'];
646             return true;
647         }
648     }
649
650     //Below are functions not used directly and exist for backwards compatibility with customizations, will be removed in a later version
651
652     /* returns array of keys for duplicate checking, first check for an index of type alternate_key, if not found searches for
653      * primary key.
654      *
655      */
656     public function _get_alternate_key_fields($table_name) {
657         $indices=Link::_get_link_table_definition($table_name,'indices');
658         if (!empty($indices)) {
659             foreach ($indices as $index) {
660                 if ( isset($index['type']) && $index['type'] == 'alternate_key' ) {
661                     return $index['fields'];
662                 }
663             }
664         }
665         //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.
666         $relDef = $this->relationship->def;
667         if (!empty($relDef['join_key_lhs']) && !empty($relDef['join_key_rhs']))
668             return array($relDef['join_key_lhs'], $relDef['join_key_rhs']);
669     }
670
671     /**
672      * @deprecated
673      * Gets the vardef for the relationship of this link.
674      */
675     public function _get_link_table_definition($table_name, $def_name) {
676
677         if (isset($this->relationship->def[$def_name]))
678             return $this->relationship->def[$def_name];
679
680         return null;
681     }
682
683     /**
684      * @deprecated
685      * Return the name of the role field for the passed many to many table.
686      * if there is no role filed : return false
687      * @param $table_name name of relationship table to inspect
688      * @return bool|string
689      */
690     public function _get_link_table_role_field($table_name) {
691         $varDefs = $this->_get_link_table_definition($table_name, 'fields');
692         $role_field = false;
693         if(!empty($varDefs)){
694             $role_field = '';
695             foreach($varDefs as $v){
696                 if(strpos($v['name'], '_role') !== false){
697                     $role_field = $v['name'];
698                 }
699             }
700         }
701         return $role_field;
702     }
703
704     /**
705      * @deprecated
706      *
707      * @return boolean returns true if this link is LHS
708      */
709     public function _get_bean_position()
710     {
711         return $this->getSide() == REL_LHS;
712     }
713 }