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 ********************************************************************************/
38 /*********************************************************************************
40 * Description: Defines the base class for new data type, Relationship, methods in the class will
41 * be used to manipulate relationship between object instances.
42 * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
43 * All Rights Reserved.
44 * Contributor(s): ______________________________________..
45 ********************************************************************************/
51 /* Private variables.*/
53 var $_relationship_name; //relationship this attribute is tied to.
54 var $_bean; //stores a copy of the bean.
55 var $_relationship= '';
56 var $_bean_table_name;
57 var $_bean_key_name='id';
58 private $relationship_fields = array();
60 var $_swap_sides = false;
61 var $_rhs_key_override = false;
62 var $_bean_filter_field = '';
64 //if set to true role column will not be added to the filter criteria.
65 var $ignore_role_filter=false;
66 //if set to true distinct clause will be added to the select list.
67 var $add_distinct=false;
68 //value of this variable dictates the action to be taken when a duplicate relationship record is found.
69 //1-ignore,2-update,3-delete.
70 //var $when_dup_relationship_found=2; // deprecated - only used by Queues, which is also no longer used
72 // a value for duplicate variable is stored by the _relatinship_exists method.
74 var $_duplicate_where;
77 * $_rel_name: use this relationship key.
78 * $_bean: reference of the bean that instantiated this class.
79 * $_fieldDef: vardef entry for the field.
80 * $_table_name: optional, fetch from the bean's table name property.
81 * $_key_name: optional, name of the primary key column for _table_name
83 function Link($_rel_name, &$_bean, $fieldDef, $_table_name='', $_key_name=''){
84 $GLOBALS['log']->debug("Link Constructor, relationship name: ".$_rel_name);
85 $GLOBALS['log']->debug("Link Constructor, Table name: ".$_table_name);
86 $GLOBALS['log']->debug("Link Constructor, Key name: ".$_key_name);
87 //_pp(func_get_args());
88 $this->_relationship_name=$_rel_name;
89 $this->relationship_fields = (!empty($fieldDef['rel_fields']))?$fieldDef['rel_fields']: array();
91 $this->_relationship=new Relationship();
92 //$this->_relationship->retrieve_by_string_fields(array('relationship_name'=>$this->_relationship_name));
93 $this->_relationship->retrieve_by_name($this->_relationship_name);
95 $this->_db = DBManagerFactory::getInstance();
98 //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.
99 //the property values can be altered again before any requests are made.
100 if (!empty($fieldDef) && is_array($fieldDef)) {
101 if (array_key_exists('ignore_role', $fieldDef)) {
102 if ($fieldDef['ignore_role'] == true) {
103 $this->ignore_role_filter=true;
104 $this->add_distinct=true;
109 $this->_bean_table_name=(!empty($_table_name)) ? $_table_name : $_bean->table_name;
110 if (!empty($key_name)) {
111 $this->_bean_key_name=$_key_name;
114 if ($this->_relationship->lhs_table != $this->_relationship->rhs_table) {
116 if ($_bean->table_name == $this->_relationship->lhs_table)
117 $this->_bean_key_name=$this->_relationship->lhs_key;
119 if ($_bean->table_name == $this->_relationship->rhs_table)
120 $this->_bean_key_name=$this->_relationship->rhs_key;
125 if ($this->_relationship->lhs_table == $this->_relationship->rhs_table && isset($fieldDef['side']) && $fieldDef['side'] == 'right'){
126 $this->_swap_sides = true;
129 if (!empty($fieldDef['rhs_key_override'])) {
130 $this->_rhs_key_override = true;
132 if (!empty($fieldDef['bean_filter_field'])) {
133 $this->_bean_filter_field = $fieldDef['bean_filter_field'];
136 //default to id if not set.
137 if (empty($this->_bean_key_name))$this->_bean_key_name='id';
139 $GLOBALS['log']->debug("Link Constructor, _bean_table_name: ".$this->_bean_table_name);
140 $GLOBALS['log']->debug("Link Constructor, _bean_key_name: ".$this->_bean_key_name);
141 if (!empty($this->_relationship->id)) $GLOBALS['log']->debug("Link Constructor, relationship record found.");
142 else $GLOBALS['log']->debug("Link Constructor, No relationship record.") ;
146 /* This method will return the following based on cardinality of the relationship.
147 * # one-to-many, many-to-many: empty array if not data is found else array of keys.
148 * # if many-to-many and $role set to true : empty array if not data is found else
149 * array of array which contain id+other fields.
150 * # many-to-one, one-to-one: null if no linked data is found, else key value.
152 * For a self referencing relationship the function will behave as if the user is trying
153 * to access the child records. To get to the parent records use the getParent() method.
155 function get($role = false) {
157 $role_field = $this->_get_link_table_role_field($this->_relationship_name);
158 if($role_field !== false){
159 $query = $this->getQuery(false, array(),0, "", false, "", $role_field);
164 $query = $this->getQuery();
166 $result = $this->_db->query($query, true);
168 while($row = $this->_db->fetchByAssoc($result))
173 $list[] = $row['id'];
179 function getRelatedTableName() {
181 $bean_is_lhs=$this->_get_bean_position();
182 if (!isset($bean_is_lhs)) {
183 $GLOBALS['log']->debug("Invalid relationship parameters. Exiting..");
188 return $this->_relationship->rhs_table;
190 return $this->_relationship->lhs_table;
194 function getRelatedModuleName() {
196 $bean_is_lhs=$this->_get_bean_position();
197 if (!isset($bean_is_lhs)) {
198 $GLOBALS['log']->debug("Invalid relationship parameters. Exiting..");
203 return $this->_relationship->rhs_module;
205 return $this->_relationship->lhs_module;
210 function getRelatedFields(){
211 return $this->relationship_fields;
214 function getRelatedField($name){
215 return (!empty($this->relationship_fields[$name]))? $this->relationship_fields[$name]: null;
218 function getRelationshipObject() {
219 return $this->_relationship;
222 function _get_bean_position() {
223 //current beans module and table are on the left side or the right side.
225 if ($this->_relationship->lhs_table == $this->_bean_table_name && $this->_relationship->lhs_key == $this->_bean_key_name) {
229 if ($this->_relationship->rhs_table == $this->_bean_table_name && $this->_relationship->rhs_key == $this->_bean_key_name) {
233 if($this->_swap_sides){
239 function _is_self_relationship() {
240 if ($this->_relationship->lhs_table == $this->_relationship->rhs_table) {
246 function getJoin($params, $return_array =false)
248 $join_type= ' INNER JOIN ';
249 if(isset($params['join_type'])){
250 $join_type = $params['join_type'];
254 $bean_is_lhs=$this->_get_bean_position();
256 if ($this->_relationship->relationship_type=='one-to-one' or $this->_relationship->relationship_type=='many-to-one' or
257 ($this->_relationship->relationship_type=='one-to-many' && !$bean_is_lhs))
260 $table = $this->_relationship->rhs_table;
261 $key = $this->_relationship->rhs_key;
262 // check right table alias
263 $other_table = (empty($params['left_join_table_alias']) ? $this->_relationship->lhs_table : $params['left_join_table_alias']);
264 $other_key = $this->_relationship->lhs_key;
266 $key = $this->_relationship->lhs_key;
267 $table = $this->_relationship->lhs_table;
269 if ( ! empty($params['join_table_alias']))
271 $table_with_alias = $table. " ".$params['join_table_alias'];
272 $table = $params['join_table_alias'];
274 $other_table = (empty($params['right_join_table_alias']) ? $this->_relationship->rhs_table : $params['right_join_table_alias']);
275 $other_key = $this->_relationship->rhs_key;
278 $join = $join_type . ' '. $table_with_alias . " ON\n".$table.'.'.$key.'= '.$other_table.'.'.$other_key ." AND ". $table.".deleted=0\n";
281 if ($this->_relationship->relationship_type == 'one-to-many' && $bean_is_lhs) {
283 $table = $this->_relationship->rhs_table;
284 $key = $this->_relationship->rhs_key;
285 $other_table = (empty($params['left_join_table_alias']) ? $this->_relationship->lhs_table : $params['left_join_table_alias']);
286 $other_key = $this->_relationship->lhs_key;
287 if ( ! empty($params['join_table_alias']))
289 $table_with_alias = $table. " ".$params['join_table_alias'];
290 $table = $params['join_table_alias'];
293 $join = $join_type . ' '.$table_with_alias . " ON\n".$table.'.'.$key.'= '.$other_table.'.'.$other_key ." AND ". $table.".deleted=0\n";
297 if ($this->_relationship->relationship_type=='many-to-many' )
299 if ( ! empty($params['join_table_alias']))
301 $table_with_alias = $this->_relationship->join_table. " ".$params['join_table_alias'];
302 $table = $params['join_table_alias'];
303 $rel_table_with_alias =
304 $this->_relationship->join_table. " ".
305 $params['join_table_link_alias'];
306 $rel_table = $params['join_table_link_alias'];
311 $other_table = (empty($params['left_join_table_alias']) ? $this->_relationship->lhs_table : $params['left_join_table_alias']);
312 $join .= $join_type . ' '.$rel_table_with_alias.' ON '.$other_table.".".$this->_relationship->lhs_key."=".$rel_table.".".$this->_relationship->join_key_lhs." AND ".$rel_table.".deleted=0\n";
315 $other_table = (empty($params['right_join_table_alias']) ? $this->_relationship->rhs_table : $params['right_join_table_alias']);
316 $join .= $join_type . ' '.$rel_table_with_alias.' ON '.$other_table.".".$this->_relationship->rhs_key."=".$rel_table.".".$this->_relationship->join_key_rhs." AND ".$rel_table.".deleted=0\n";
318 if (!empty($this->_relationship->relationship_role_column) && !$this->ignore_role_filter)
320 $join.=" AND ".$rel_table.'.'.$this->_relationship->relationship_role_column;
322 if (empty($this->_relationship->relationship_role_column_value))
326 $join.= "='".$this->_relationship->relationship_role_column_value."'";
330 if ( ! empty($params['join_table_alias']))
334 $table_with_alias = $this->_relationship->rhs_table. " ".$params['join_table_alias'];
336 $table_with_alias = $this->_relationship->lhs_table. " ".$params['join_table_alias'];
338 $table = $params['join_table_alias'];
343 if($this->_rhs_key_override){
344 $join .= $join_type . ' '.$table_with_alias.' ON '.$table.".".$this->_relationship->rhs_key."=".$rel_table.".".$this->_relationship->join_key_rhs." AND ".$table.".deleted=0";
346 $join .= $join_type . ' '.$table_with_alias.' ON '.$table.".".$this->_relationship->lhs_key."=".$rel_table.".".$this->_relationship->join_key_rhs." AND ".$table.".deleted=0";
349 $join .= $join_type . ' '.$table_with_alias.' ON '.$table.".".$this->_relationship->rhs_key."=".$rel_table.".".$this->_relationship->join_key_lhs." AND ".$table.".deleted=0";
356 $ret_arr['join'] = $join;
357 $ret_arr['type'] = $this->_relationship->relationship_type;
360 $ret_arr['rel_key'] = $this->_relationship->join_key_rhs;
363 $ret_arr['rel_key'] = $this->_relationship->join_key_lhs;
371 function _add_deleted_clause($deleted=0,$add_and='',$prefix='') {
373 if (!empty($prefix)) $prefix.='.';
374 if (!empty($add_and)) $add_and=' '.$add_and.' ';
376 if ($deleted==0) return $add_and.$prefix.'deleted=0';
377 if ($deleted==1) return $add_and.$prefix.'deleted=1';
381 function _add_optional_where_clause($optional_array, $add_and='',$prefix='') {
383 if (!empty($prefix)) $prefix.='.';
384 if (!empty($add_and)) $add_and=' '.$add_and.' ';
386 if(!empty($optional_array)){
387 return $add_and.$prefix."".$optional_array['lhs_field']."".$optional_array['operator']."'".$optional_array['rhs_value']."'";
390 //end function _add_optional_where_clause
395 function getQuery($return_as_array=false, $sort_array = array(),$deleted=0, $optional_where="", $return_join = false, $bean_filter="", $role="", $for_subpanels = false){
401 $join_tables = array();
402 $bean_is_lhs=$this->_get_bean_position();
404 if (!isset($bean_is_lhs)) {
405 $GLOBALS['log']->debug("Invalid relationship parameters. Exiting..");
409 if (empty($bean_filter)) {
410 if(!empty($this->_bean_filter_field)){
411 $bean_filter_field = $this->_bean_filter_field;
412 $bean_filter="= '".$this->_bean->$bean_filter_field."'";
414 $bean_filter="= '".$this->_bean->id."'";
418 $GLOBALS['log']->debug("getQuery, Bean is LHS: ".$bean_is_lhs);
419 $GLOBALS['log']->debug("getQuery, Relationship type=".$this->_relationship->relationship_type);
420 $GLOBALS['log']->debug("getQuery, Relationship role column name=".$this->_relationship->relationship_role_column);
422 if ($this->_relationship->relationship_type=='one-to-one' or $this->_relationship->relationship_type=='many-to-one' or
423 ($this->_relationship->relationship_type=='one-to-many' && !$bean_is_lhs)) {
425 $GLOBALS['log']->debug("Processing one-to-one,many-to-one,one-to-many.");
427 if ($this->add_distinct) {
428 $select='SELECT DISTINCT id';
434 $from= 'FROM '.$this->_relationship->rhs_table;
435 $where='WHERE '.$this->_relationship->rhs_table.'.'.$this->_relationship->rhs_key.$bean_filter;
436 if (!empty($this->_relationship->relationship_role_column) && !$this->ignore_role_filter) {
437 $where.=" AND ".$this->_relationship->rhs_table.'.'.$this->_relationship->relationship_role_column;
440 if (empty($this->_relationship->relationship_role_column_value)) {
443 $where.= "='".$this->_relationship->relationship_role_column_value."'";
447 //add deleted clause - but not if we're dealing with a Custom table which will lack the 'deleted' field
448 if(substr_count($this->_relationship->rhs_table, '_cstm') == 0)
449 $where.=$this->_add_deleted_clause($deleted,'AND',$this->_relationship->rhs_table );
451 if($optional_where!=""){
452 //process optional where
453 $where.=$this->_add_optional_where_clause($optional_where,'AND');
459 $from= 'FROM '.$this->_relationship->lhs_table;
460 $where='WHERE '.$this->_relationship->lhs_table.'.'.$this->_relationship->lhs_key."= '".$this->_bean->{$this->_relationship->rhs_key}."'";
461 //added deleted clause.
462 $where.=$this->_add_deleted_clause($deleted,'AND', $this->_relationship->lhs_table);
465 if($optional_where!=""){
466 //process optional where
467 $where.=$this->_add_optional_where_clause($optional_where,'AND');
472 if ($this->_relationship->relationship_type == 'one-to-many' && $bean_is_lhs) {
474 $GLOBALS['log']->debug("Processing one-to-many.");
476 if ($this->add_distinct) {
477 $select='SELECT DISTINCT '.$this->_relationship->rhs_table.'.id';
479 $select='SELECT '.$this->_relationship->rhs_table.'.id';
481 $from= 'FROM '.$this->_relationship->rhs_table;
482 $where='WHERE '.$this->_relationship->rhs_table.'.'.$this->_relationship->rhs_key.$bean_filter;
483 if (!empty($this->_relationship->relationship_role_column) && !$this->ignore_role_filter) {
484 $where.=" AND ".$this->_relationship->rhs_table.'.'.$this->_relationship->relationship_role_column;
486 if (empty($this->_relationship->relationship_role_column_value)) {
489 $where.= "='".$this->_relationship->relationship_role_column_value."'";
493 //add deleted clause - but not if we're dealing with a Custom table which will lack the 'deleted' field
494 if(substr_count($this->_relationship->rhs_table, '_cstm') == 0)
495 $where.=$this->_add_deleted_clause($deleted,'AND',$this->_relationship->rhs_table);
497 if($optional_where!=""){
498 //process optional where
499 $where.=$this->_add_optional_where_clause($optional_where,'AND');
504 if ($this->_relationship->relationship_type=='many-to-many' ) {
505 $GLOBALS['log']->debug("Processing many-to-many.");
507 $swap = !$for_subpanels && $this->_swap_sides;
508 if (($bean_is_lhs && !$swap) || (!$bean_is_lhs && $swap)) {
509 if ($this->add_distinct) {
510 $select="SELECT DISTINCT ".$this->_relationship->rhs_table.".id";
512 $select="SELECT ".$this->_relationship->rhs_table.".id";
515 $from= 'FROM '.$this->_relationship->rhs_table;
516 $subjoin=' INNER JOIN '.$this->_relationship->join_table.' ON ('.$this->_relationship->rhs_table.".".$this->_relationship->rhs_key."=".$this->_relationship->join_table.".".$this->_relationship->join_key_rhs." AND ".$this->_relationship->join_table.".".$this->_relationship->join_key_lhs.$bean_filter;
517 $join_tables[] = $this->_relationship->join_table;
518 if (!empty($this->_relationship->relationship_role_column) && !$this->ignore_role_filter) {
519 $subjoin.=" AND ".$this->_relationship->join_table.'.'.$this->_relationship->relationship_role_column;
522 if (empty($this->_relationship->relationship_role_column_value)) {
523 $subjoin.=' IS NULL';
525 $subjoin.= "='".$this->_relationship->relationship_role_column_value."'";
533 //add deleted clause.
534 if ($deleted == 0 or $deleted==1) {
535 $where.=' WHERE '.$this->_add_deleted_clause($deleted,'',$this->_relationship->join_table).$this->_add_deleted_clause($deleted,'AND',$this->_relationship->rhs_table);
539 if($optional_where!=""){
540 //process optional where
541 $where.=$this->_add_optional_where_clause($optional_where,'AND', $this->_relationship->rhs_table);
547 if ($this->add_distinct) {
548 $select="SELECT DISTINCT ".$this->_relationship->lhs_table.".id";
550 $select="SELECT ".$this->_relationship->lhs_table.".id";
553 $from= 'FROM '.$this->_relationship->lhs_table;
554 $subjoin=' INNER JOIN '.$this->_relationship->join_table.' ON ('.$this->_relationship->lhs_table.".".$this->_relationship->lhs_key."=".$this->_relationship->join_table.".".$this->_relationship->join_key_lhs." AND ".$this->_relationship->join_table.".".$this->_relationship->join_key_rhs.$bean_filter;
555 $join_tables[] = $this->_relationship->join_table;
556 if (!empty($this->_relationship->relationship_role_column) && !$this->ignore_role_filter) {
557 $subjoin.=" AND ".$this->_relationship->relationship_role_column;
560 if (empty($this->_relationship->relationship_role_column_value)) {
561 $subjoin.=' IS NULL';
563 $subjoin.= "='".$this->_relationship->relationship_role_column_value."'";
569 //add deleted clause.
570 if ($deleted == 0 or $deleted==1) {
571 $where.=' WHERE '.$this->_add_deleted_clause($deleted,'',$this->_relationship->join_table).$this->_add_deleted_clause($deleted,'AND',$this->_relationship->lhs_table);
575 if($optional_where!=""){
576 //process optional where
577 $where.=$this->_add_optional_where_clause($optional_where,'AND', $this->_relationship->lhs_table);
582 $select.=", ".$this->_relationship->join_table.".".$role;
585 if ($return_as_array) {
586 $query_as_array['select']=$select;
587 $query_as_array['from']=$from;
588 $query_as_array['where']=$where;
590 $query_as_array['join'] = $join;
591 $query_as_array['join_tables'] = $join_tables;
593 return $query_as_array;
596 $query = $select.' '.$from.' '.$where;
597 $GLOBALS['log']->debug("Link Query=".$query);
602 function getBeans($template, $sort_array = array(), $begin_index = 0, $end_index = -1, $deleted=0, $optional_where="") {
603 $query = $this->getQuery(false,array(), $deleted, $optional_where); //get array of IDs
604 return $this->_bean->build_related_list($query, $template);
607 function _add_one_to_many_table_based($key,$bean_is_lhs) {
610 $set_key_value=$this->_bean->id;
611 $where_key_value=$key;
615 $where_key_value=$this->_bean->id;
618 $query= 'UPDATE '.$this->_relationship->rhs_table;
619 $query.=' SET '.$this->_relationship->rhs_table.'.'.$this->_relationship->rhs_key."='".$set_key_value."'";
621 //add role column to the query.
622 if (!empty($this->_relationship->relationship_role_column)) {
623 $query.=' ,'.$this->_relationship->relationship_role_column."='".$this->_relationship->relationship_role_column_value."'";
625 $query.=' WHERE '.$this->_relationship->rhs_table.".id='".$where_key_value."'";
627 $GLOBALS['log']->debug("Relationship Query ".$query);
629 $result=$this->_db->query($query, true);
632 /* handles many to one*/
633 function _add_many_to_one_bean_based($key) {
635 //make a copy of this bean to avoid recursion.
636 $bean=new $this->_bean->object_name;
637 $bean->retrieve($this->_bean->id);
639 $bean->{$this->_relationship->lhs_key}=$key;
641 //set relationship role.
642 if (!empty($this->_relationship->relationship_role_column)) {
643 $bean->{$this->_relationship->relationship_role_column}=$this->_relationship->relationship_role_column_value;
649 /* use this function to create link between 2 objects
650 * 1:1 will be treated like 1 to many.
651 * todo handle self referencing relationships
652 * the function also allows for setting of values for additional field in the table being
653 * updated to save the relationship, in case of many-to-many relationships this would be the join table.
654 * the values should be passed as key value pairs with column name as the key name and column value as key value.
656 function add($rel_keys,$additional_values=array()) {
658 if (!isset($rel_keys) or empty($rel_keys)) {
659 $GLOBALS['log']->debug("Link.add, Null key passed, no-op, returning... ");
663 //check to ensure that we do in fact have an id on the bean.
664 if(empty($this->_bean->id)){
665 $GLOBALS['log']->debug("Link.add, No id on the bean... ");
669 if (!is_array($rel_keys)) {
675 $bean_is_lhs=$this->_get_bean_position();
676 if (!isset($bean_is_lhs)) {
677 $GLOBALS['log']->debug("Invalid relationship parameters. Exiting..");
680 //if multiple keys are passed then check for unsupported relationship types.
681 if (count($keys) > 1) {
682 if (($this->_relationship->relationship_type == 'one-to-one')
683 or ($this->_relationship->relationship_type == 'one-to-many' and !$bean_is_lhs)
684 or ($this->_relationship->relationship_type == 'many-to-one')) {
685 $GLOBALS['log']->error("Invalid parameters passed to function, the relationship does not support addition of multiple records.");
689 $GLOBALS['log']->debug("Relationship type = {$this->_relationship->relationship_type}");
690 foreach($keys as $key) {
692 //fetch the related record using the key and update.
693 if ($this->_relationship->relationship_type=='one-to-one' or $this->_relationship->relationship_type == 'one-to-many') {
694 $this->_add_one_to_many_table_based($key,$bean_is_lhs);
697 //updates the bean passed to the instance....
698 //todo remove this case.
699 if ($this->_relationship->relationship_type=='many-to-one') {
700 $this->_add_many_to_one_bean_based($key);
703 //insert record in the link table.
704 if ($this->_relationship->relationship_type=='many-to-many' ) {
706 //Swap the bean positions for self relationships not coming from subpanels.
707 //such as one-to-many relationship fields generated in studio/MB
708 $swap = !isset($_REQUEST['subpanel_id']) && $this->_swap_sides;
709 //add keys from the 2 tables to the additional keys array..
710 if (($bean_is_lhs && !$swap) || (!$bean_is_lhs && $swap)) {
711 $additional_values[$this->_relationship->join_key_lhs]=$this->_bean->id;
712 $additional_values[$this->_relationship->join_key_rhs]=$key;
714 $additional_values[$this->_relationship->join_key_rhs]=$this->_bean->id;
715 $additional_values[$this->_relationship->join_key_lhs]=$key;
717 //add the role condition.
718 if (!empty($this->_relationship->relationship_role_column) && !empty($this->_relationship->relationship_role_column_value)) {
719 $additional_values[$this->_relationship->relationship_role_column]=$this->_relationship->relationship_role_column_value;
721 //add deleted condition.
722 $additional_values['deleted']=0;
724 $this->_add_many_to_many($additional_values);
726 //reverse will be set to true only for self-referencing many-to-many relationships.
727 if ($this->_is_self_relationship() && !empty($GLOBALS['dictionary'][$this->_relationship_name]) &&
728 !empty($GLOBALS['dictionary'][$this->_relationship_name]['true_relationship_type']) &&
729 $GLOBALS['dictionary'][$this->_relationship_name]['true_relationship_type'] == 'many-to-many' ||
730 (!empty($this->_relationship->reverse) && $this->_relationship->reverse == true )){
732 $temp=$additional_values[$this->_relationship->join_key_lhs];
733 $additional_values[$this->_relationship->join_key_lhs]=$additional_values[$this->_relationship->join_key_rhs];
734 $additional_values[$this->_relationship->join_key_rhs]=$temp;
736 $this->_add_many_to_many($additional_values);
739 $custom_logic_arguments = array();
740 $custom_reverse_arguments = array();
741 $custom_logic_arguments['related_id'] = $key;
742 $custom_logic_arguments['id'] = $this->_bean->id;
743 $custom_reverse_arguments['related_id'] = $this->_bean->id;
744 $custom_reverse_arguments['id'] = $key;
746 $custom_logic_arguments['module'] = $this->_relationship->lhs_module;
747 $custom_logic_arguments['related_module'] = $this->_relationship->rhs_module;
748 $custom_reverse_arguments['module'] = $this->_relationship->rhs_module;
749 $custom_reverse_arguments['related_module'] = $this->_relationship->lhs_module;
751 $custom_logic_arguments['related_module'] = $this->_relationship->lhs_module;
752 $custom_reverse_arguments['related_module'] = $this->_relationship->rhs_module;
753 $custom_logic_arguments['module'] = $this->_relationship->rhs_module;
754 $custom_reverse_arguments['module'] = $this->_relationship->lhs_module;
756 /**** CALL IT FROM THE MAIN BEAN FIRST ********/
757 $this->_bean->call_custom_logic('after_relationship_add', $custom_logic_arguments);
758 /**** NOW WE HAVE TO CALL THE LOGIC HOOK THE OTHER WAY SINCE IT TAKES TWO FOR A RELATIONSHIP****/
760 if ( isset($beanList[$custom_logic_arguments['related_module']]) ) {
761 $class = $beanList[$custom_logic_arguments['related_module']];
762 if ( !empty($class) ) {
763 $rbean = new $class();
765 $rbean->call_custom_logic('after_relationship_add', $custom_reverse_arguments);
771 function _add_many_to_many($add_values) {
774 $add_values['date_modified']= $GLOBALS['timedate']->nowDb();
776 //check whether duplicate exist or not.
777 if ($this->relationship_exists($this->_relationship->join_table,$add_values)) {
779 /* switch($this->when_dup_relationship_found) {
781 case 1: //do nothing.
782 $GLOBALS['log']->debug("Executing default option, no action.");
785 case 3: //delete the record first, then create a new entry.
786 $this->_delete_row($this->_relationship->join_table,$this->_duplicate_key);
787 $this->_insert_row($add_values);
791 case 2: //update the record.
792 */ $this->_update_row($add_values,$this->_relationship->join_table,$this->_duplicate_where);
797 $this->_insert_row($add_values);
801 function _delete_row($table_name,$key) {
802 $query="UPDATE $table_name SET deleted=1, date_modified='" .$GLOBALS['timedate']->nowDb()."' WHERE id='$key'";
803 $GLOBALS['log']->debug("Relationship Delete Statement :".$query);
805 $result=$this->_db->query($query, true);
808 function _update_row(&$value_array,$table_name,$where) {
810 $query='UPDATE '.$table_name.' SET ';
812 foreach ($value_array as $key=>$value) {
813 $query.=$delimiter.$key."='".$value."' ";
817 $GLOBALS['log']->debug("Relationship Update Statement :".$query);
819 $result=$this->_db->query($query, true);
822 function _insert_row(&$value_array) {
824 $value_array['id']= create_guid();
829 foreach ($value_array as $key=>$value) {
830 $columns_list.=$delimiter.$key;
831 $values_list .=$delimiter."'".$value."'";
834 $insert_string='INSERT into '.$this->_relationship->join_table.' ('.$columns_list.') VALUES ('.$values_list.')';
835 $GLOBALS['log']->debug("Relationship Insert String :".$insert_string);
837 $result=$this->_db->query($insert_string, true);
842 /* this method operates on all related record, takes action based on cardinality of the relationship.
843 * one-to-one, one-to-many: update the rhs table's parent id with null
844 * many-to-one: update the lhs table's parent-id with null.
845 * many-to-many: delete rows from the link table. related table must have delted and date_modified column.
846 * if related_is is null, the methods assumes that the parent bean (whose id is passed) is being deleted.
847 * if both id and related_id are passed the metod unlinks a single relationship.
848 * parameters: id of the bean being deleted.
851 function delete($id,$related_id='') {
852 $GLOBALS['log']->debug(sprintf("delete called with these parameter values. id=%s, related_id=%s",$id,$related_id));
854 $_relationship=&$this->_relationship;
855 $_bean=&$this->_bean;
857 $bean_is_lhs=$this->_get_bean_position();
858 if (!isset($bean_is_lhs)) {
859 $GLOBALS['log']->debug("Invalid relationship parameters. Exiting..");
862 if ($_relationship->relationship_type=='one-to-many' or $_relationship->relationship_type=='one-to-one' ) {
864 //update rhs_table set rhs_key = null, relation_column_name = null where rhs_key= this_bean_id
865 $query='UPDATE '.$_relationship->rhs_table.' SET '.$_relationship->rhs_key."=NULL, date_modified='".$GLOBALS['timedate']->nowDb()."'";
867 if (!empty($_relationship->relationship_role_column) && !empty($_relationship->relationship_role_column_value)) {
868 $query.=','.$_relationship->relationship_role_column."= NULL ";
869 $query.=' WHERE '.$_relationship->relationship_role_column."= '".$_relationship->relationship_role_column_value."' AND ";
873 $query.=$_relationship->rhs_key."= '".$id."' ";
875 //restrict to one row if related_id is passed.
876 if (!empty($related_id)) {
877 $query.=" AND ".$_relationship->rhs_table.".id='".$related_id."'";
882 //do nothing because the row that stores the relationship keys is being deleted.
883 //todo log an error message here.
884 //if this is the case and related_id is passed then log a message asking the user
885 //to clear the relationship using the bean.
889 if ($_relationship->relationship_type=='many-to-one') {
890 //do nothing because the row that stores the relationship keys is being deleted.
891 //todo log an error message here.
892 //if this is the case and related_id is passed then log a message asking the user
893 //to clear the relationship using the bean.
896 if ($_relationship->relationship_type=='many-to-many' ) {
897 $use_bean_is_lhs = isset($_REQUEST['ajaxSubpanel']) || $this->_swap_sides !== true;
898 $query='UPDATE '.$_relationship->join_table." SET deleted=1, date_modified='".$GLOBALS['timedate']->nowDb()."'";
899 if ($bean_is_lhs && $use_bean_is_lhs) {
900 if (!empty($this->_relationship->reverse) && ($this->_relationship->reverse == true or $this->_relationship->reverse == 1)){
901 if (empty($related_id)) {
902 $query.=" WHERE (".$_relationship->join_key_lhs."= '". $id ."' or ".$_relationship->join_key_rhs."='". $id ."')" ;
904 $query.=" WHERE (".$_relationship->join_key_lhs."= '". $id ."' AND ".$_relationship->join_key_rhs."='".$related_id."') OR (".$_relationship->join_key_rhs."='". $id ."' AND ".$_relationship->join_key_lhs."='".$related_id."')";
907 if (empty($related_id)) {
908 $query.=" WHERE ".$_relationship->join_key_lhs."= '". $id ."'";
910 $query.=" WHERE ".$_relationship->join_key_lhs."= '". $id ."' AND ".$_relationship->join_key_rhs."= '". $related_id."'";
914 if (!empty($this->_relationship->reverse) && ($this->_relationship->reverse == true or $this->_relationship->reverse == 1)) {
915 if (empty($related_id)) {
916 $query.=" WHERE (".$_relationship->join_key_rhs."= '". $id ."' or ".$_relationship->join_key_lhs."='". $id ."')" ;
918 $query.=" WHERE (".$_relationship->join_key_rhs."= '". $id ."' AND ".$_relationship->join_key_lhs."='".$related_id."') OR (".$_relationship->join_key_lhs."='". $id ."' AND ".$_relationship->join_key_rhs."='".$related_id."')";
921 if (empty($related_id)) {
922 $query.=" WHERE ".$_relationship->join_key_rhs."= '". $id ."'" ;
924 $query.=" WHERE ".$_relationship->join_key_rhs."= '". $id ."' AND ".$_relationship->join_key_lhs."= '". $related_id."'" ;
927 if (!empty($_relationship->relationship_role_column) && !empty($_relationship->relationship_role_column_value)) {
928 $query.=' AND '.$_relationship->relationship_role_column."= '".$_relationship->relationship_role_column_value."'";
932 //if query string is not empty execute it.
934 $GLOBALS['log']->debug('Link.Delete:Delete Query: '.$query);
935 $this->_db->query($query,true);
937 $custom_logic_arguments = array();
938 $custom_logic_arguments['id'] = $id;
939 $custom_logic_arguments['related_id'] = $related_id;
940 $custom_reverse_arguments = array();
941 $custom_reverse_arguments['related_id'] = $id;
942 $custom_reverse_arguments['id'] = $related_id;
944 $custom_logic_arguments['module'] = $this->_relationship->lhs_module;
945 $custom_logic_arguments['related_module'] = $this->_relationship->rhs_module;
946 $custom_reverse_arguments['module'] = $this->_relationship->lhs_module;
947 $custom_reverse_arguments['related_module'] = $this->_relationship->rhs_module;
949 $custom_logic_arguments['module'] = $this->_relationship->rhs_module;
950 $custom_logic_arguments['related_module'] = $this->_relationship->lhs_module;
951 $custom_reverse_arguments['module'] = $this->_relationship->lhs_module;
952 $custom_reverse_arguments['related_module'] = $this->_relationship->rhs_module;
955 if (empty($this->_bean->id)) {
956 $this->_bean->retrieve($id);//!$bean_is_lhs || empty($related_id) ? $id : $related_id);
958 $this->_bean->call_custom_logic('after_relationship_delete', $custom_logic_arguments);
959 //NOW THE REVERSE WAY SINCE A RELATIONSHIP TAKES TWO
961 if ( isset($beanList[$custom_logic_arguments['related_module']]) ) {
962 $class = $beanList[$custom_logic_arguments['related_module']];
963 if ( !empty($class) ) {
964 $rbean = new $class();
965 $rbean->retrieve(empty($related_id) ? $id : $related_id);
966 $rbean->call_custom_logic('after_relationship_delete', $custom_reverse_arguments);
971 function relationship_exists($table_name, $join_key_values) {
973 //find the key values for the table.
974 $dup_keys=$this->_get_alternate_key_fields($table_name);
975 if (empty($dup_keys)) {
976 $GLOBALS['log']->debug("No alternate key define, skipping duplicate check..");
981 $this->_duplicate_where=' WHERE ';
982 foreach ($dup_keys as $field) {
983 //look for key in $join_key_values, if found add to filter criteria else abort duplicate checking.
984 if (isset($join_key_values[$field])) {
986 $this->_duplicate_where .= $delimiter.' '.$field."='".$join_key_values[$field]."'";
989 $GLOBALS['log']->error('Duplicate checking aborted, Please supply a value for this column '.$field);
994 $this->_duplicate_where .= $delimiter.' deleted=0';
996 $query='SELECT id FROM '.$table_name.$this->_duplicate_where;
998 $GLOBALS['log']->debug("relationship_exists query(".$query.')');
1000 $result=$this->_db->query($query, true);
1001 $row = $this->_db->fetchByAssoc($result);
1007 $this->_duplicate_key=$row['id'];
1012 /* returns array of keys for duplicate checking, first check for an index of type alternate_key, if not found searches for
1016 function _get_alternate_key_fields($table_name) {
1018 $indices=Link::_get_link_table_definition($table_name,'indices');
1019 if (!empty($indices)) {
1020 foreach ($indices as $index) {
1021 if ( isset($index['type']) && $index['type'] == 'alternate_key' ) {
1022 return $index['fields'];
1026 $relationships=Link::_get_link_table_definition($table_name,'relationships');
1027 if (!empty($relationships)) {//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.
1028 if(!empty($relationships[$this->_relationship_name]) && !empty($relationships[$this->_relationship_name]['join_key_lhs']) && !empty($relationships[$this->_relationship_name]['join_key_rhs'])) {
1029 return array($relationships[$this->_relationship_name]['join_key_lhs'], $relationships[$this->_relationship_name]['join_key_rhs']);
1036 function _get_link_table_definition($table_name,$def_name) {
1039 include_once('modules/TableDictionary.php');
1040 // first check to see if already loaded - assumes hasn't changed in the meantime
1041 if (isset($dictionary[$table_name][$def_name]))
1043 return $dictionary[$table_name][$def_name];
1046 if (isset($dictionary[$this->_relationship_name][$def_name])) {
1047 return ($dictionary[$this->_relationship_name][$def_name]);
1049 // custom metadata is found in custom/metadata (naturally) and the naming follows the convention $relationship_name_c, and $relationship_name = $table_name$locations = array( 'metadata/' , 'custom/metadata/' ) ;
1050 $relationshipName = preg_replace( '/_c$/' , '' , $table_name ) ;
1052 $locations = array ( 'metadata/' , 'custom/metadata/' ) ;
1054 foreach ( $locations as $basepath )
1056 $path = $basepath . $relationshipName . 'MetaData.php' ;
1058 if (file_exists($path))
1061 if (isset($dictionary[$relationshipName][$def_name])) {
1062 return $dictionary[$relationshipName][$def_name];
1066 // couldn't find the metadata for the table in either the standard or custom locations
1067 $GLOBALS['log']->debug('Error fetching field defs for join table '.$table_name);
1076 * Return the name of the role field for the passed many to many table.
1077 * if there is no role filed : return false
1079 function _get_link_table_role_field($table_name) {
1080 $varDefs = $this->_get_link_table_definition($table_name, 'fields');
1081 $role_field = false;
1082 if(!empty($varDefs)){
1084 foreach($varDefs as $v){
1085 if(strpos($v['name'], '_role') !== false){
1086 $role_field = $v['name'];