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.
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.
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
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
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.
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.
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 ********************************************************************************/
40 /*********************************************************************************
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.
46 * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
47 * All Rights Reserved.
48 * Contributor(s): ______________________________________..
49 ********************************************************************************/
51 require_once("data/Relationships/RelationshipFactory.php");
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();
65 * @param $linkName String name of a link field in the module's vardefs
66 * @param $bean SugarBean focus bean for this link (one half of a relationship)
70 function __construct($linkName, $bean, $linkDef = false){
72 if (empty($bean->field_defs) || empty($bean->field_defs[$linkName]) || empty($bean->field_defs[$linkName]['relationship']))
76 //Assume $linkName is really relationship_name, and find the link name with the vardef manager
77 $this->def = VardefManager::getLinkFieldForRelationship($bean->module_dir, $bean->object_name, $linkName);
80 $this->def = $linkDef;
82 if (is_array($this->def) && !isset($this->def['name']))
84 //More than one link found, we need to figure out if we are currently on the LHS or RHS
86 if (isset($this->def[0]['side']) && $this->def[0]['side'] == 'left')
88 $this->def = $this->def[0];
89 }else if (isset($this->def[1]['side']) && $this->def[1]['side'] == 'left')
91 $this->def = $this->def[1];
94 $this->def = $this->def[0];
97 if (empty($this->def['name']))
99 $GLOBALS['log']->fatal("failed to find link for $linkName");
103 $this->name = $this->def['name'];
105 $this->def = $bean->field_defs[$linkName];
106 $this->name = $linkName;
108 $this->relationship = SugarRelationshipFactory::getInstance()->getRelationship($this->def['relationship']);
110 if (!$this->loadedSuccesfully()) {
111 $GLOBALS['log']->fatal("{$this->name} for {$this->def['relationship']} failed to load\n");
113 //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.
114 //the property values can be altered again before any requests are made.
115 if (!empty($this->def) && is_array($this->def)) {
116 if (array_key_exists('ignore_role', $this->def)) {
117 if ($this->def['ignore_role']) {
118 $this->ignore_role_filter=true;
119 $this->add_distinct=true;
125 public function loadedSuccesfully()
127 return !empty($this->relationship);
130 public function load()
132 $data = $this->relationship->load($this);
133 $this->rows = $data['rows'];
135 $this->loaded = true;
138 /* This method will return the following based on cardinality of the relationship.
139 * # one-to-many, many-to-many: empty array if not data is found else array of keys.
140 * # if many-to-many and $role set to true : empty array if not data is found else
141 * array of array which contain id+other fields.
142 * # many-to-one, one-to-one: null if no linked data is found, else key value.
144 * For a self referencing relationship the function will behave as if the user is trying
145 * to access the child records. To get to the parent records use the getParent() method.
147 public function get($role = false) {
151 return array_keys($this->rows);
159 public function getRelatedTableName() {
160 return BeanFactory::getBean($this->getRelatedModuleName())->table_name;
163 public function getRelatedModuleName() {
164 if (!$this->relationship) return false;
166 if ($this->getSide() == REL_LHS) {
167 return $this->relationship->getRHSModule();
169 return $this->relationship->getLHSModule();
173 public function getType()
175 switch ($this->relationship->type)
182 return $this->getSide() == REL_LHS ? "many" : "one";
187 public function getFocus()
198 public function getRelatedFields(){
199 return $this->relationship_fields;
202 public function getRelatedField($name){
203 if (!empty($this->relationship_fields) && !empty($this->relationship_fields[$name]))
204 return $this->relationship_fields[$name];
206 return null; //For now return null. Later try the relationship object directly.
209 public function getRelationshipObject() {
210 return $this->relationship;
213 public function getSide() {
214 //First try the relationship
215 if ($this->relationship->getLHSLink() == $this->name &&
216 ($this->relationship->getLHSModule() == $this->focus->module_name)
221 if ($this->relationship->getRHSLink() == $this->name &&
222 ($this->relationship->getRHSModule() == $this->focus->module_name)
227 //Next try the vardef
228 if (!empty($this->def['side']))
230 if ((strtolower($this->def['side']) == 'left' || $this->def['side'] == REL_LHS)
231 //Some relationships make have left in the vardef errorneously if generated by module builder
232 && $this->name != $this->relationship->def['join_key_lhs'])
241 $GLOBALS['log']->error("Unable to get proper side for link {$this->name}");
244 protected function is_self_relationship() {
245 return $this->relationship->isSelfReferencing();
248 public function isParentRelationship(){
249 return $this->relationship->isParentRelationship();
252 function getJoin($params, $return_array =false)
254 return $this->relationship->getJoin($this, $params, $return_array);
257 function getQuery($params = array())
259 return $this->relationship->getQuery($this, $params);
262 public function getSubpanelQuery($params = array(), $return_array = false)
264 return $this->relationship->getSubpanelQuery($this, $params, $return_array);
267 function getBeans() {
268 if (!$this->loaded) {
271 if(!is_array($this->beans))
273 $this->beans = array();
274 $rel_module = $this->getRelatedModuleName();
275 foreach ($this->rows as $id => $vals)
277 $this->beans[$id] = BeanFactory::getBean($rel_module, $id);
285 * use this function to create link between 2 objects
286 * 1:1 will be treated like 1 to many.
288 * todo handle self referencing relationships
290 * the function also allows for setting of values for additional field in the table being
291 * updated to save the relationship, in case of many-to-many relationships this would be the join table.
293 * @param array $rel_keys array of ids or SugarBean objects
294 * @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.
298 function add($rel_keys,$additional_values=array()) {
299 if (!is_array($rel_keys))
300 $rel_keys = array($rel_keys);
302 foreach($rel_keys as $key)
304 if (!($key instanceof SugarBean)) {
305 $key = $this->getRelatedBean($key);
306 if (!($key instanceof SugarBean)) {
307 $GLOBALS['log']->error("Unable to load related bean by id");
312 if(empty($key->id) || empty($this->focus->id))
315 if ($this->getSide() == REL_LHS) {
316 $this->relationship->add($this->focus, $key, $additional_values);
319 $this->relationship->add($key, $this->focus, $additional_values);
326 /* this method operates on all related record, takes action based on cardinality of the relationship.
327 * one-to-one, one-to-many: update the rhs table's parent id with null
328 * many-to-one: update the lhs table's parent-id with null.
329 * many-to-many: delete rows from the link table. related table must have delted and date_modified column.
330 * if related_is is null, the methods assumes that the parent bean (whose id is passed) is being deleted.
331 * if both id and related_id are passed the metod unlinks a single relationship.
332 * parameters: id of the bean being deleted.
335 function delete($id, $related_id='') {
336 if (empty($this->focus->id))
337 $this->focus = BeanFactory::getBean($this->focus->module_name, $id);
338 if (!empty($related_id))
340 if (!($related_id instanceof SugarBean)) {
341 $related_id = $this->getRelatedBean($related_id);
343 if ($this->getSide() == REL_LHS) {
344 $this->relationship->remove($this->focus, $related_id);
347 $this->relationship->remove($related_id, $this->focus);
352 $this->relationship->removeAll($this);
356 protected function getRelatedBean($id = false)
358 return BeanFactory::getBean($this->getRelatedModuleName(), $id);
361 function relationship_exists($table_name, $join_key_values) {
363 //find the key values for the table.
364 $dup_keys=$this->_get_alternate_key_fields($table_name);
365 if (empty($dup_keys)) {
366 $GLOBALS['log']->debug("No alternate key define, skipping duplicate check..");
371 $this->_duplicate_where=' WHERE ';
372 foreach ($dup_keys as $field) {
373 //look for key in $join_key_values, if found add to filter criteria else abort duplicate checking.
374 if (isset($join_key_values[$field])) {
376 $this->_duplicate_where .= $delimiter.' '.$field."='".$join_key_values[$field]."'";
379 $GLOBALS['log']->error('Duplicate checking aborted, Please supply a value for this column '.$field);
384 $this->_duplicate_where .= $delimiter.' deleted=0';
386 $query='SELECT id FROM '.$table_name.$this->_duplicate_where;
388 $GLOBALS['log']->debug("relationship_exists query(".$query.')');
390 $result=$this->_db->query($query, true);
391 $row = $this->_db->fetchByAssoc($result);
397 $this->_duplicate_key=$row['id'];
402 /* returns array of keys for duplicate checking, first check for an index of type alternate_key, if not found searches for
406 protected function _get_alternate_key_fields($table_name) {
407 $indices=Link::_get_link_table_definition($table_name,'indices');
408 if (!empty($indices)) {
409 foreach ($indices as $index) {
410 if ( isset($index['type']) && $index['type'] == 'alternate_key' ) {
411 return $index['fields'];
415 //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.
416 $relDef = $this->relationship->def;
417 if (!empty($relDef['join_key_lhs']) && !empty($relDef['join_key_rhs']))
418 return array($relDef['join_key_lhs'], $relDef['join_key_rhs']);
423 protected function _get_link_table_definition($table_name, $def_name) {
426 if (isset($this->relationship->def[$def_name]))
427 return $this->relationship->def[$def_name];
432 * Return the name of the role field for the passed many to many table.
433 * if there is no role filed : return false
435 protected function _get_link_table_role_field($table_name) {
436 $varDefs = $this->_get_link_table_definition($table_name, 'fields');
438 if(!empty($varDefs)){
440 foreach($varDefs as $v){
441 if(strpos($v['name'], '_role') !== false){
442 $role_field = $v['name'];
452 * @return boolean returns true if this link is LHS
454 public function _get_bean_position()
456 return $this->getSide() == REL_LHS;
460 public function &__get($name)
464 case "relationship_type":
465 return $this->relationship->type;
466 case "_relationship":
467 return $this->relationship;
469 if (!is_array($this->beans))
476 public function __set($name, $val)
484 * @param SugarBean $bean
487 public function addBean($bean)
489 if (!is_array($this->beans))
491 $this->beans[$bean->id] = $bean;
495 * @param SugarBean $bean
498 public function removeBean($bean)
500 if (!is_array($this->beans))
502 unset($this->beans[$bean->id]);
503 unset($this->rows[$bean->id]);