]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - data/Link2.php
Release 6.5.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-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         return BeanFactory::getBean($this->getRelatedModuleName(), $id);
535     }
536
537     public function &__get($name)
538     {
539         switch($name)
540         {
541             case "relationship_type":
542                 return $this->relationship->type;
543             case "_relationship":
544                 return $this->relationship;
545             case "beans":
546                 if (!is_array($this->beans))
547                     $this->getBeans();
548                 return $this->beans;
549             case "rows":
550                 if (!is_array($this->rows))
551                     $this->load();
552                 return $this->rows;
553         }
554         return $this->$name;
555     }
556
557     public function __set($name, $val)
558     {
559         if($name == "beans")
560             $this->beans = $val;
561
562     }
563
564     /**
565      * Add a bean object to the list of beans currently loaded to this relationship.
566      * This for the most part should not need to be called except by the relatipnship implementation classes.
567      * @param SugarBean $bean
568      * @return void
569      */
570     public function addBean($bean)
571     {
572         if (!is_array($this->beans))
573         {
574             $this->tempBeans[$bean->id] = $bean;
575         }
576         else {
577             $this->beans[$bean->id] = $bean;
578         }
579
580     }
581
582     /**
583      * Remove a bean object from the list of beans currently loaded to this relationship.
584      * This for the most part should not need to be called except by the relatipnship implementation classes.
585      *
586      * @param SugarBean $bean
587      * @return void
588      */
589     public function removeBean($bean)
590     {
591         if (!is_array($this->beans) && isset($this->tempBeans[$bean->id]))
592         {
593             unset($this->tempBeans[$bean->id]);
594         } else {
595             unset($this->beans[$bean->id]);
596             unset($this->rows[$bean->id]);
597         }
598     }
599
600
601     /**
602      * Directly queries the databse for set of values. The relationship classes and not link should handle this.
603      * @deprecated
604      * @param $table_name string relationship table
605      * @param $join_key_values array of key=>values to identify this relationship by
606      * @return bool true if the given join key set exists in the relationship table
607      */
608     public function relationship_exists($table_name, $join_key_values) {
609
610         //find the key values for the table.
611         $dup_keys=$this->_get_alternate_key_fields($table_name);
612         if (empty($dup_keys)) {
613             $GLOBALS['log']->debug("No alternate key define, skipping duplicate check..");
614             return false;
615         }
616
617         $delimiter='';
618         $this->_duplicate_where=' WHERE ';
619         foreach ($dup_keys as $field) {
620             //look for key in  $join_key_values, if found add to filter criteria else abort duplicate checking.
621             if (isset($join_key_values[$field])) {
622
623                 $this->_duplicate_where .= $delimiter.' '.$field."='".$join_key_values[$field]."'";
624                 $delimiter='AND';
625             } else {
626                 $GLOBALS['log']->error('Duplicate checking aborted, Please supply a value for this column '.$field);
627                 return false;
628             }
629         }
630         //add deleted check.
631         $this->_duplicate_where .= $delimiter.' deleted=0';
632
633         $query='SELECT id FROM '.$table_name.$this->_duplicate_where;
634
635         $GLOBALS['log']->debug("relationship_exists query(".$query.')');
636
637         $result=$this->_db->query($query, true);
638         $row = $this->_db->fetchByAssoc($result);
639
640         if ($row == null) {
641             return false;
642         }
643         else {
644             $this->_duplicate_key=$row['id'];
645             return true;
646         }
647     }
648
649     //Below are functions not used directly and exist for backwards compatibility with customizations, will be removed in a later version
650
651     /* returns array of keys for duplicate checking, first check for an index of type alternate_key, if not found searches for
652      * primary key.
653      *
654      */
655     public function _get_alternate_key_fields($table_name) {
656         $indices=Link::_get_link_table_definition($table_name,'indices');
657         if (!empty($indices)) {
658             foreach ($indices as $index) {
659                 if ( isset($index['type']) && $index['type'] == 'alternate_key' ) {
660                     return $index['fields'];
661                 }
662             }
663         }
664         //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.
665         $relDef = $this->relationship->def;
666         if (!empty($relDef['join_key_lhs']) && !empty($relDef['join_key_rhs']))
667             return array($relDef['join_key_lhs'], $relDef['join_key_rhs']);
668     }
669
670     /**
671      * @deprecated
672      * Gets the vardef for the relationship of this link.
673      */
674     public function _get_link_table_definition($table_name, $def_name) {
675
676         if (isset($this->relationship->def[$def_name]))
677             return $this->relationship->def[$def_name];
678
679         return null;
680     }
681
682     /**
683      * @deprecated
684      * Return the name of the role field for the passed many to many table.
685      * if there is no role filed : return false
686      * @param $table_name name of relationship table to inspect
687      * @return bool|string
688      */
689     public function _get_link_table_role_field($table_name) {
690         $varDefs = $this->_get_link_table_definition($table_name, 'fields');
691         $role_field = false;
692         if(!empty($varDefs)){
693             $role_field = '';
694             foreach($varDefs as $v){
695                 if(strpos($v['name'], '_role') !== false){
696                     $role_field = $v['name'];
697                 }
698             }
699         }
700         return $role_field;
701     }
702
703     /**
704      * @deprecated
705      *
706      * @return boolean returns true if this link is LHS
707      */
708     public function _get_bean_position()
709     {
710         return $this->getSide() == REL_LHS;
711     }
712 }