1<?php 2 /** 3 * SQL Generator - helps to construct queries statements 4 * @author Edgar Antonio Luna Diaz <eald@co.com.mx> 5 * @author Alejadro Borges 6 * @author Jonathan Alberto Rivera Gomez 7 * @copyright Copyright (C) 2003,2004 Free Software Foundation, Inc. http://www.fsf.org/ 8 * @license http://www.fsf.org/licenses/gpl.html GNU General Public License 9 * @package phpgwapi 10 * @subpackage database 11 * @version $Id: class.sql_builder.inc.php 21045 2010-03-25 22:41:38Z Caeies $ 12 * @internal Development of this application was funded by http://www.sogrp.com 13 * @link http://www.sogrp.com/ 14 */ 15 16 /** 17 * SQL entity name 18 */ 19 define(PHPGW_SQL_ENTITY_NAME, 0); 20 /** 21 * SQL distance 22 */ 23 define(PHPGW_SQL_DISTANCE, 1); 24 /** 25 * SQL child fields 26 */ 27 define(PHPGW_SQL_CHILD_FIELDS, 2); 28 /** 29 * SQL change distance 30 */ 31 define(PHPGW_SQL_CHANGE_DISTANCE, 3); 32 /** 33 * SQL sql lazy key 34 */ 35 define(PHPGW_SQL_LAZY_KEY, 1); 36 /** 37 * SQL required key 38 */ 39 define(PHPGW_SQL_REQUIRED_KEY, 2); 40 /** 41 * SQL all match 42 */ 43 define(PHPGW_SQL_ALL_MATCH, 1); 44 /** 45 * SQL exclusive match 46 */ 47 define(PHPGW_SQL_EXCLUSIVE_MATCH, 2); 48 49 50 /** 51 * SQL Generator - helps to construct queries statements 52 * 53 * This class provide common methods to create transaction sql queries. 54 * @package phpgwapi 55 * @subpackage database 56 * @abstract 57 */ 58 class sql_builder 59 { 60 // Map have a list of false_fields 61 // false_field => ('entity to instantiate', 'distance to central table'); 62 var $map; 63 var $limit_str; 64 var $ondebug; 65 var $entities; 66 var $distance; 67 var $path; 68 var $reverse_path; 69 var $all_criteria; 70 var $central_entity; 71 var $operation; 72 var $external_select_value; 73 var $external_from_value; 74 var $external_criteria_value; 75 var $_criteria_built = False; 76 /*************************************************************\ 77 * Entity, class and general section * 78 \*************************************************************/ 79 80 /** 81 * Class contructor 82 */ 83 function sql_builder() 84 { 85 } 86 87 /** 88 * Instance a entity if this not exist 89 * 90 * @param string $field The field name 91 * @return string The class which was instanced. 92 */ 93 function get_entity($field) 94 { 95 $ent = $this->ent_name($field); 96 $this->ldebug('get_entity', array('Field' => $field, 97 'Entity Name' => $ent)); 98 if(!is_object($this->entities[$ent]) && !empty($ent)) 99 { 100 $this->ldebug('get_entity', "Try to instantiate $ent because $field", 'msg'); 101 $this->instance_entity($ent); 102 $this->distance[$ent] = $this->get_distance($field); 103 } 104 return $ent; 105 } 106 107 /** 108 * @param string $field The field name for search in $this->map for a class8 109 * @return integer The distance of the class that have that field defined. 110 */ 111 function get_distance($field) 112 { 113 $entity = $this->map[$field][PHPGW_SQL_ENTITY_NAME]; 114 if(is_array($this->distance)) 115 { 116 $distance = array_keys($this->distance); 117 if(in_array($entity, $distance)) 118 { 119 return $this->distance[$entity]; 120 } 121 } 122 return $this->map[$field][PHPGW_SQL_DISTANCE]; 123 } 124 125 /** 126 * Add an entity to map for building queries 127 * 128 * This function waits the map variable of entity 129 * @param string $entity Name of entity and class that will be in the query development. 130 * @param mixed $map The same $map that is set in one entity class 131 * @param integer $distance To the `$entity' to central_entity 132 * <code> 133 * $contact = CreateObject('phpgwapi.contacts'); 134 * $my_ent = CreateObject('my_app.my_entity'); 135 * $contact->add_entity('my_entity', $my_ent->map, 20); 136 * </code> 137 */ 138 function add_entity($entity, $map, $distance = 200) 139 { 140 $fields = array_keys($map); 141 foreach ($fields as $field) 142 { 143 $this->map[$field] = array($entity, $distance); 144 } 145 } 146 147 /** 148 * This function set in the distance array the distance of the entity. 149 * 150 * caveat: need a field :/ 151 * @param string $field The field name for search in $this->map 152 * @param string $ent The name of the entity which want set it's entitiy 153 */ 154 function set_distance($field, $ent) 155 { 156 $this->distance[$ent] = $this->map[$field][PHPGW_SQL_DISTANCE]; 157 } 158 159 /** 160 * Instance entity based on the information on map 161 * 162 * @param string $field A false field name that could be found in the map and have and entity declared. 163 * @return array Form: 'entity_name' => entity_object 164 */ 165 function instance_entity_by_field($field) 166 { 167 $entity_name = $this->ent_name($field); 168 $entity = CreateObject($entity_name); 169 return array($entity_name, &$entity); 170 } 171 172 function instance_entity($entity_name) 173 { 174 if(is_object($this->entities[$entity_name])) 175 { 176 $this->ldebug('instance_entity', 177 "Already an object $entity_name", 'msg'); 178 } 179 else 180 { 181 $this->entities[$entity_name] = CreateObject($entity_name); 182 $this->ldebug('instance_entity', 183 "Creating object $entity_name", 'msg'); 184 } 185 $this->ldebug('instance_entity', array('Entity name' => $entity_name)); 186 } 187 188 /*************************************************************\ 189 * Short path section * 190 \*************************************************************/ 191 192 function sort_by_distances() 193 { 194 // Re-sort the distances of clases that will use 195 if(!is_array($this->entities) || empty($this->entities)) 196 { 197 return; 198 } 199 arsort($this->distance); 200 $entity_distance = current($this->distance); 201 $entity_far_away = key($this->distance); 202 $this->ldebug('sort_by_distances', 'entering to sort_by_distance', 'msg'); 203 $this->add_path($entity_far_away, TRUE); 204 $this->ldebug('sort_by_distances', 205 array('Distances' => $this->distance, 206 'Path' => $this->path, 207 'Entity Distance' => $entity_distance, 208 'Entity far away' => $entity_far_away), 209 'dump'); 210 $this->entity_to_center($entity_far_away, 211 $entity_distance, TRUE); 212 $this->merge_paths(); 213 $this->ldebug('sort_by_distances', 'entering to while, all the add_path must be reverse from here', 'msg'); 214 while(!($this->last_distance())) 215 { 216 if (empty($this->distance)) 217 { 218 // Array finished? 219 break; 220 } 221 next($this->distance); 222 $entity_distance = current($this->distance); 223 $entity_far_away = key($this->distance); 224 $this->ldebug('sort_by_distances', array('Distance' => $entity_distance,'Name' => $entity_far_away)); 225 // We already found his path 226 if (array_key_exists($entity_far_away, $this->path)) 227 { 228 $this->ldebug('sort_by_distances', array('Already in path' => $entity_far_away)); 229 continue; 230 } 231 // We reach to central entity, this ends the game. 232 if ($entity_distance != 0) 233 { 234 $this->ldebug('sort_by_distances', array('Distance2' => $entity_distance,'Name' => $entity_far_away)); 235 $this->add_path($entity_far_away, TRUE); 236 $this->entity_to_center($entity_far_away, 237 $entity_distance, TRUE); 238 $this->ldebug('sort_by_distances', 239 array('Path' => array_keys($this->path)), 'dump'); 240 $this->merge_paths(); 241 } 242 } 243 } 244 245 /** 246 * This create a path of any identity to central entity 247 * 248 * @param string $entiity_name The name of the entity we try to find his path. 249 * @param int $entity_distance The distance of the $entity_name. 250 * @param bool $reverse TRUE if we want that path found be sorted in reverse mode (that is what we want for all case except first one). 251 */ 252 function entity_to_center($entity_name, $entity_distance, $reverse=FALSE) 253 { 254 $this->ldebug('entity_to_center', array('Entity_name' => $entity_name)); 255 if ($this->last_distance()) 256 { 257 return; 258 } 259 $links = array_merge($this->entities[$entity_name]->get_ilinks(), 260 $this->entities[$entity_name]->get_elinks()); 261 $this->ldebug('entity_to_center', array('Links' => $links), 'dump'); 262 $min_distance = $entity_distance; 263 foreach ($links as $entity => $link) 264 { 265 $field = $link['ffield']; 266 $dist = $this->get_distance($field); 267 $this->ldebug('entity_to_center', 268 array('field' => $field, 269 'Min distance' => $min_distance, 270 'entity this iteration' => $entity, 271 'Actual distance' => $dist)); 272 $tmp = array_keys($this->get_path()); 273 if ($dist < $min_distance || ($min_distance == $entity_distance && !in_array($entity, $tmp))) 274 { 275 //We found a good one 276 $this->ldebug('entity_to_center', 277 "Actual Distance $dist < Min Distance $min_distance", 278 'msg'); 279 $min_distance = $dist; 280 $near_entity = $this->ent_name($field); 281 } 282 } 283 284 $this->instance_entity($near_entity); 285 if(is_array($this->distance)) 286 { 287 if(array_key_exists($near_entity, $this->distance)) 288 { 289 unset($this->distance[$near_entity]); 290 } 291 } 292 $this->add_path($near_entity, $reverse); 293 if ($min_distance == 0) 294 { 295 $this->central_entity = $near_entity; 296 } 297 else 298 { 299 $this->entity_to_center($near_entity, $min_distance, $reverse); 300 } 301 } 302 303 function last_distance() 304 { 305 $curr = key($this->distance); 306 @$this->ldebug('last_distance', array('Before Current' => $curr), 'dump'); 307 next($this->distance); 308 if (@key($this->distance)) 309 { 310 $curr = key($this->distance); 311 $this->ldebug('last_distance', array('Next' => $curr), 'dump'); 312 prev($this->distance); 313 $curr = key($this->distance); 314 $this->ldebug('last_distance', array('Current No last' => $curr), 'dump'); 315 return FALSE; 316 } 317 else 318 { 319 $curr = key($this->distance); 320 $this->ldebug('last_distance', array('Next' => $curr), 'dump'); 321 prev($this->distance); 322 $curr = key($this->distance); 323 $this->ldebug('last_distance', array('Current LAST' => $curr), 'dump'); 324 return TRUE; 325 } 326 } 327 328 function add_path($ent, $reverse = FALSE) 329 { 330 $this->ldebug('add_path', array('Entity'=> $ent, 331 'Reverse' => $reverse)); 332 if ($reverse) 333 { 334 $this->false_path[$ent]['identity'] = $this->entities[$ent]->get_identity(); 335 $this->false_path[$ent]['alias'] = $this->entities[$ent]->get_alias(); 336 } 337 else 338 { 339 $this->path[$ent]['identity'] = $this->entities[$ent]->get_identity(); 340 $this->path[$ent]['alias'] = $this->entities[$ent]->get_alias(); 341 } 342 } 343 344 function get_path() 345 { 346 return is_array($this->path) ? $this->path : array(); 347 } 348 349 function merge_paths() 350 { 351 $this->path = $this->get_path() + array_reverse($this->false_path); 352 $this->false_path = array(); 353 } 354 355 /** 356 * Return the SQL select correct for all the entity map. 357 * 358 * This is the main functionality of this class. 359 * @return String with the sql created 360 */ 361 function get_sql() 362 { 363 if(!is_array($this->entities) || empty($this->entities)) 364 { 365 return; 366 } 367 $this->ldebug('get_sql', 368 array('Entities' => array_keys($this->entities), 369 'Path' => $this->distance), 370 'dump'); 371 $this->sort_by_distances(); 372 foreach ($this->entities as $name => $class) 373 { 374 list($fields, $from, $alias, $lcriteria) = $class->get_select(); 375 if ($fields) 376 { 377 $this->select_fields[] = $fields; 378 } 379 $fields = ''; 380 if ($lcriteria && !$this->_criteria_built) 381 { 382 $this->all_criteria = sql_criteria::append_and(array($lcriteria, $this->all_criteria)); 383 } 384 } 385 $from = $this->get_join(); 386 $this->fields = implode(', ', $this->select_fields); 387 // hooks for add external things in my queries 388 $this->fields = $this->external_select($this->fields); 389 $from = $this->external_from ($from); 390 $this->all_criteria = $this->external_criteria($this->all_criteria); 391 $sql = 'SELECT '.$this->fields.' FROM '.$from; 392 $sql .= (empty($this->all_criteria))?'':' WHERE '. $this->all_criteria; 393 $this->select_fields = NULL; 394 $this->all_criteria = NULL; 395 $this->entities = NULL; 396 $this->path = NULL; 397 $this->distance = NULL; 398 $this->false_path = NULL; 399 $this->_criteria_built = False; 400 401 $sql = $this->set_order($sql); 402 unset($this->order_string); 403 $this->ldebug('SQL', array('SQL String' => $sql)); 404 return $sql; 405 } 406 407 /** 408 * Allow to add fields to select part 409 * 410 * @param string $select Internal select part 411 * @return string complete SELECT field list 412 * @private 413 */ 414 function external_select($select) 415 { 416 return $select.(($this->external_select_value) ? ' '.$this->external_select_value : ''); 417 } 418 419 /** 420 * Allow to add joins to from clausule 421 * 422 * @param string $from Internal joins 423 * @return string complete joins 424 * @private 425 */ 426 function external_from($from) 427 { 428 return $from.(($this->external_from_value) ? ' '.$this->external_from_value : ''); 429 } 430 431 /** 432 * Allow to add criterias to internal WHERE 433 * 434 * @param string|array $criteria token list or string that have the external criterias 435 * @return string complete criterias 436 * @private 437 */ 438 function external_criteria($criteria) 439 { 440 if(!is_array($this->external_criteria_value)) 441 { 442 return $criteria.(($this->external_criteria_value) ? ' '.$this->external_criteria_value : ''); 443 } 444 else 445 { 446 $this->external_criteria_value[1] = $criteria; 447 return sql_criteria::criteria($this->external_criteria_value); 448 } 449 } 450 451 // This code is the base for calling insert procedure, maybe be 452 // integrated in get_sql, too ambicious? maybe. 453 function get_insert() 454 { 455 $this->insert_entities = keys($this->entities); 456 while (!empty($this->insert_entities)) 457 { 458 $this->get_dependendencies(current($this->insert_entities)); 459 } 460 } 461 462 function get_dependencies($entity) 463 { 464 // Get the classes that we depend on 465 $pre_depends = $this->entities[$entity]->get_ilinks(); 466 $entities_depends = keys($pre_depends); 467 foreach($entities_depends as $entity_depend) 468 { 469 if(array_key_exists($entity_depend, $this->entities)) 470 { 471 //We depend in entity that is in array 472 $this->get_dependencies($entity_depend); 473 $this->set_insert_dependency($entity, $entity_depend); 474 } 475 } 476 $this->add_insert_array($entity); 477 } 478 479 /** 480 * Get the list of false fields for $entity 481 * 482 * @param string $entity name of entity to search fields 483 * @return array list of false fields of $entity 484 */ 485 function get_false_fields($entity) 486 { 487 if (is_object($this->entities[$entity_name])) 488 { 489 return $this->entities[$entity_name]->get_false_fields(); 490 } 491 else 492 { 493 $entity = CreateObject($entity); 494 $fields = $entity->get_false_fields(); 495 return $fields; 496 } 497 } 498 499 /** 500 * Use $this->path for the order, and create the joins of all identities in the query 501 * 502 * @return all necesary for FROM clause (the joins by magic). 503 */ 504 function get_join() 505 { 506 $path = $this->get_path(); 507 // from + 508 // table as alias (1) 509 $this->ldebug('get_join', 510 array('Path' => $path), 511 'dump'); 512 $prev_entity = key($path); 513 $prev_data = current($path); 514 $from = $prev_data['identity']; 515 array_shift($path); 516 // repeat in all $path 517 while (list($entity, $data) = each($path)) 518 { 519 // get_link, will determine the type of JOIN, 520 // local_field, foreign_field, and the right alias, 521 // and identity. 522 $link = $this->find_link($entity, $prev_entity,$prev_data['alias']); 523 524 // If we found the way to know when use other kind 525 // of join change next line. 526 // INNER JOIN table as alias 527 $this->ldebug('get_join', 528 array('entity' => $entity, 529 'prev_entity' => $prev_entity, 530 'link' => $link), 531 'dump'); 532 $from .= $link['join'].' JOIN '.$data['identity']; 533 // ON alias.lfield = prev_alias.ffield 534 $from .= ' ON '.$data['alias'].'.'. 535 $this->real_field($entity,$link['lfield']); 536 $from .= ' = '.$link['alias'].'.'. 537 $this->real_field($link['prev_entity'], 538 $link['ffield']); 539 // In a strange world, this variable would be useful 540 // if don't unset it, I'm under drugs right now, just 541 // leave the note. 542 $prev_entity = $entity; 543 $prev_data = $data; 544 } 545 return $from; 546 } 547 548 function find_link($entity, $prev_entity, $alias) 549 { 550 $this->ldebug('find_link', array('prev_entity' => $prev_entity, 'entity' => $entity)); 551 if($return_link = $this->get_link($entity, $prev_entity)) 552 { 553 $this->ldebug('find_link', 'I get a link', 'msg'); 554 $return_link['prev_entity'] = $prev_entity; 555 $return_link['alias'] = $alias; 556 } 557 elseif($return_link = $this->get_link($entity, $this->central_entity)) 558 { 559 $this->ldebug('find_link', 'I get to center', 'msg'); 560 $return_link['prev_entity'] = $this->central_entity; 561 $return_link['alias'] = $this->path[$this->central_entity]['alias']; 562 $this->ldebug('find_link', array('return_link' => $return_link), 'dump'); 563 } 564 else 565 { 566 // Maybe here is one amazing research about the nodes 567 // entities, but not for this moment. 568 return; 569 } 570 $this->ldebug('find_link', array('return_link' => $return_link), 'dump'); 571 return $return_link; 572 } 573 574 function get_link($entity, $test_entity) 575 { 576 $this->ldebug('get_link', array('entity' => $entity, 'test_entity' => $test_entity)); 577 if ($entity_link = $this->entities[$entity]->get_ilink($test_entity)) 578 { 579 $entity_link['join'] = ($entity_link['type'] == PHPGW_SQL_REQUIRED_KEY)? 580 ' INNER ':' LEFT '; 581 $this->ldebug('get_link', "ilink natural $entity -> $test_entity", 'msg'); 582 } 583 elseif ($entity_link = $this->entities[$test_entity]->get_ilink($entity)) 584 { 585 $this->ldebug('get_link', array('link' => $entity_link), 'dump'); 586 $entity_link['join'] = ($entity_link['type'] == PHPGW_SQL_REQUIRED_KEY)? 587 ' INNER ':' RIGHT '; 588 $tmp_field = $entity_link['ffield']; 589 $entity_link['ffield'] = $entity_link['lfield']; 590 $entity_link['lfield'] = $tmp_field; 591 $this->ldebug('get_link', array('link' => $entity_link), 'dump'); 592 $this->ldebug('get_link', 'ilink reverse', 'msg'); 593 } 594 else 595 { 596 return ; 597 } 598 return $entity_link; 599 } 600 601 /** 602 * Get the real name for $field 603 * 604 * @param string $ent entity name 605 * @param string $field false field name 606 * return string with real_name 607 */ 608 function real_field($ent, $field) 609 { 610 $this->ldebug('real_field', 611 array('Entity' => $ent, 612 'Field' => $field)); 613 return $this->entities[$ent]->real_field($field); 614 } 615 616 /** 617 * Get the real name for $field based on the map 618 * 619 * @param string $field false field name 620 */ 621 function real_name($field, $operation='insert') 622 { 623 list($entity_name, $entity) = $this->instance_entity_by_field($field); 624 $entity->set_operation($operation); 625 return $entity->real_field($entity->put_real_alias($field)); 626 } 627 628 /** 629 * Get the real name for $field based on the map and instantiate the entity 630 * 631 * @param string $field false field name 632 */ 633 function real_field_entity($field, $operation='') 634 { 635 list($entity, $instance) = $this->instance_entity_by_field($field); 636 $instance->set_opertation($operation); 637 $this->entities[$entity] = $entity; 638 return $instance->real_field($instance->put_real_alias($field)); 639 } 640 641 /** 642 * Cast value for datatype 643 * 644 * @param mixed $value Which we go to cast 645 * @param string $field field name to know the datatype 646 * @param boolean $permanent decide if we want to preserv the instance of the entity in $entities array 647 */ 648 function cast($value, $field, $permanent=True) 649 { 650 $entity = $this->ent_name($field); 651 $instance = $this->instance_volatile($entity, $permanent); 652 return $instance->cast($value, $field); 653 } 654 655 656 /** 657 * Instance an entity for permant usage of for remove at instant 658 * 659 * $permanent don't have any effect if entity already exist in $entities array. 660 * @param mixed $value Which we go to cast 661 * @param string $field field name to know the datatype 662 * @param boolean $permanent decide if we want to preserv the instance of the entity in $entities array 663 */ 664 function instance_volatile($entity, $permanent=True) 665 { 666 if(is_object($this->entities[$entity])) 667 { 668 $entity =& $this->entities[$entity]; 669 return $entity; 670 } 671 else 672 { 673 $instance = CreateObject($entity); 674 if($permanent) 675 { 676 $this->entities[$entity] = $instance; 677 return $this->entities[$entity]; 678 } 679 else 680 { 681 return $instance; 682 } 683 } 684 } 685 686 /** 687 * Get the class name of field 688 * 689 * @param string $field The field name 690 * @return string with class name. 691 */ 692 function ent_name($field) 693 { 694 $this->ldebug('ent_name', array("Map[$field]" => $this->map[$field]), 695 'dump'); 696 if(isset($this->map[$field])) 697 { 698 return $this->map[$field][PHPGW_SQL_ENTITY_NAME]; 699 } 700 else 701 { 702 $this->raise_error(); 703 $this->ldebug('ent_name', array('Class Map' => $this->map), 704 'dump', 'error'); 705 $this->ldebug('ent_name', array('Field' => $field), 706 'string', 'error'); 707 return ''; 708 } 709 } 710 711 /** 712 * Generate a powerfull criteria based. 713 * 714 * Recieve $data that is an array (operand_left, operand_right, operator) 715 * If operant_left is array I call... myself :), else try to get the field name. 716 * If right operand is array can myself to, else, nothing. 717 * Third operator IS_ a operator name that send to sql::operate(); 718 * @param array $data Genertaded by calls of sql class. 719 * @param string $operation Is one of: insert, select, delete, update, if is diferent to select, then the result will not include the alias for table. 720 * @return Criteria for any sql query. 721 */ 722 function builder_criteria($token_criteria, $operation='insert') 723 { 724 $num_elements = count($token_criteria); 725 $this->_criteria_built = True; 726 switch($num_elements) 727 { 728 case 0: 729 case 1: 730 $local_criteria = $token_criteria; 731 break; 732 case 2: 733 case 3: 734 $operator = array_pop($token_criteria); 735 $left = array_shift($token_criteria); 736 $right = array_shift($token_criteria); 737 if(is_array($left) && $operator != 'in') 738 { 739 $left = $this->builder_criteria($left, $operation); 740 } 741 else 742 { 743 $entity = $this->get_entity($left); 744 $this->entities[$entity]->set_operation($operation); 745 $field = $left; 746 $left = $this->entities[$entity]->put_real_alias($this->real_field($entity, $left)); 747 } 748 if(is_array($right)) 749 { 750 if ($operator != 'in') 751 { 752 $right = $this->builder_criteria($right, $operation); 753 } 754 } 755 else 756 { 757 if($field && !($operator == 'has' || $operator == 'begin_with' || $operator == 'end_with')) 758 { 759 $right = $this->entities[$entity]->cast($right, $field); 760 } 761 } 762 if ($operator == 'append_and' || $operator == 'append_or') 763 { 764 $param = array($left, $right); 765 $local_criteria = sql_criteria::operate($operator,$param); 766 } 767 else 768 { 769 $local_criteria = sql_criteria::operate($operator,$left,$right); 770 } 771 break; 772 default: 773 $operator = array_pop($token_criteria); 774 foreach($token_criteria as $criteria) 775 { 776 $criterias[] = $this->builder_criteria($criteria, $operation); 777 } 778 $local_criteria = sql_criteria::operate($operator,$criterias); 779 } 780 return $local_criteria; 781 } 782 783 /** 784 * This is an optional criteria generator, powerfull because use sql_criteria and sql_entity 785 */ 786 function criteria_token($criteria, $operation='select') 787 { 788 $this->all_criteria = $this->builder_criteria($criteria, $operation); 789 } 790 791 /** 792 * Set the criteria to correspoding class 793 * 794 * @param Array $data with many criterias (how could I support that) 795 */ 796 function criteria($data) 797 { 798 // $this->ldebug('criteria', array('Query' => $data), 'dump'); 799 if(empty($data)) 800 { 801 $this->ldebug('criteria', 'Oh, is empty', 'msg'); 802 return; 803 } 804 if(is_array($data)) 805 { 806 foreach ($data as $field => $filter) 807 { 808 $ent = $this->get_entity($field); 809 // $this->instance_entity($ent); 810 $this->entities[$ent]->set_operation('select'); 811 $this->ldebug('criteria', array('Field' => $field, 812 'Filter' => $filter, 813 'Ent name' => $ent)); 814 $this->entities[$ent]->add_criteria($field, $filter); 815 } 816 } 817 elseif(is_string($data)) 818 { 819 $this->ldebug('criteria', 'Query a string is', 'msg'); 820 list($field, $filter) = split ('=', str_replace(' ', '', $data)); 821 $ent = $this->get_entity($field); 822 $this->entities[$ent]->add_criteria($field, $filter); 823 } 824 else 825 { 826 // This means we have problems 827 $this->ldebug('criteria', array('Data' => $data), 'dump'); 828 return; 829 } 830 } 831 832 /** 833 * Get the criteria for all instanced class 834 * 835 * @return the criteria for each entity. 836 */ 837 function get_criteria() 838 { 839 foreach($this->entities as $entity) 840 { 841 $entity->get_criteria(); 842 } 843 } 844 845 /** 846 * Allow set an alias for an entity 847 * 848 * @param string $entity The name of the entity that set alias 849 * @param string $alias The alias to set 850 */ 851 function set_alias($entity, $alias) 852 { 853 if(is_object($this->entities[$entity])) 854 { 855 $this->entities[$entity]->set_alias($alias); 856 } 857 } 858 859 // FIXME: needed? 860 function set_orders($fields) 861 { 862 if(is_array($fields)) 863 { 864 foreach($fields as $field) 865 { 866 $this->set_order($field); 867 } 868 } 869 } 870 871 /** 872 * Set field name to use in ORDER BY 873 * 874 * @param string $sql the string wiht thee sql that we want to limit 875 * @return sql 876 */ 877 function set_order($sql) 878 { 879 return (!empty($this->order_string))? $sql.$this->order_string : $sql; 880 } 881 882 /** 883 * Get the order list in string 884 * 885 * @return the order list. 886 */ 887 function get_order() 888 { 889 return implode(',', $this->order); 890 } 891 892 /** 893 * Call to set_order to set order by field 894 * 895 * @param Array $field The data's list that you wan sort (based on false fields. 896 */ 897 function order($fields = array(), $type = 'ASC') 898 { 899 foreach($fields as $field) 900 { 901 $order_fields = implode(',',$fields); 902 } 903 $this->order_string .= ' ORDER BY '. $order_fields . ' ' .$type; 904 } 905 906 function inserts($datas) 907 { 908 foreach($datas as $data => $value) 909 { 910 $this->insert($data, $value); 911 } 912 } 913 914 /** 915 * Find the class an call it, giving to her the field name 916 * 917 * @param array|string $data The $data that is requested 918 * @param array|string $value The $value that is requested 919 */ 920 function insert($data, $value = '') 921 { 922 $this->operation = 'insert'; 923 if(is_string($data)) 924 { 925 $fields = explode(',', $data); 926 } 927 elseif(is_array($data)) 928 { 929 $fields = &$data; 930 } 931 932 if(is_string($value)) 933 { 934 $values = explode(',',$value); 935 } 936 elseif(is_array($value)) 937 { 938 $values = &$value; 939 } 940 if ((count($values) != count($fields)) && $value != '') 941 { 942 //What do you think, that I'm a genious? 943 $this->raise_error(); 944 return; 945 } 946 947 if ($value != '') 948 { 949 // Waiting for array_combine in php5 950 $all_data = array(); 951 foreach($fields as $field_) 952 { 953 $t[$field_] = current($values); 954 $all_data = $all_data + $t; 955 next($values); 956 } 957 } 958 else 959 { 960 $all_data = &$data; 961 } 962 963 foreach($all_data as $field_ => $value_) 964 { 965 $this->ldebug('insert', 966 array('Data' => $field_)); 967 $this->_insert($field_, $value_); 968 } 969 } 970 971 function _insert($data, $value) 972 { 973 $ent = $this->get_entity($data); 974 $this->entities[$ent]->add_insert($data, $value); 975 } 976 977 function many_inserts($multiple_data = array()) 978 { 979 if(empty($multiple_data)) 980 { 981 $this->raise_error(); 982 return; 983 } 984 foreach($multiple_data as $key => $insert_array) 985 { 986 $this->insert_indexed($key, $insert_array); 987 } 988 } 989 990 function insert_indexed($key, $data) 991 { 992 foreach($data as $field => $data) 993 { 994 $ent = $this->get_entity($field); 995 $this->entities[$ent]->add_insert_index($field, $data, $key); 996 } 997 } 998 999 /** 1000 * Run the insert method of all instanced class 1001 * 1002 * @return The insert for each entity 1003 */ 1004 function run_insert() 1005 { 1006 foreach($this->entities as $entity) 1007 { 1008 array_push($this->sql_inserts, $entity->insert()); 1009 } 1010 return $this->sql_inserts; 1011 } 1012 1013 function updates($datas) 1014 { 1015 foreach($datas as $data => $value) 1016 { 1017 $this->update($data, $value); 1018 } 1019 } 1020 1021 /** 1022 * Find the class an call it, giving to her the field name 1023 * 1024 * @param string $data The $data that is requested. ie. false field 1025 * @param string|array $value The $value that is requested 1026 */ 1027 function update($data, $value) 1028 { 1029 $ent = $this->get_entity($data); 1030 $this->entities[$ent]->add_update($data, $value); 1031 } 1032 1033 /** 1034 * Run the update method of all instanced class 1035 * 1036 * @return The update for each entity 1037 */ 1038 function run_update() 1039 { 1040 foreach($this->entities as $entity) 1041 { 1042 array_push($this->sql_updates, $entity->update()); 1043 } 1044 return $this->sql_updates; 1045 } 1046 1047 /** 1048 * Delete a record according to criteria. 1049 * 1050 * For this delete we can only support one criteria, this is because coding the function that get the 1051 * right fields names and parse a tree of operations is complex (in O(n^y) terms), so it will do this less 1052 * scalable. I just decided dropped this feature for delete operations. 1053 * @param Array $criteria Form: (Field, value) or string with `field = value' 1054 * @param integer $action PHPGW_SQL_RETURN_SQL | PHPGW_SQL_RUN_SQL 1055 * @return string SQL string 1056 */ 1057 function _delete($criteria, $action=PHPGW_SQL_RETURN_SQL) 1058 { 1059 if(is_string($criteria)) 1060 { 1061 //Hope that app-developer know what sends 1062 list($field,$value) = explode(',',$criteria); 1063 if(empty($field) || empty($value)) 1064 { 1065 $GLOBALS['phpgw']->log->error(array('text'=>'E-ArgError, err: Wrong argument $criteria' .$criteria, 1066 'p1'=>'This is a grave Error', 1067 'file'=>__FILE__, 1068 'line'=>__LINE__)); 1069 } 1070 } 1071 else 1072 { 1073 $field = array_shift($criteria); 1074 $value = array_shift($criteria); 1075 } 1076 $entity = $this->instance_volatile($this->ent_name($field), False); 1077 $real_name = $entity->real_field($field); 1078 $criteria = $entity->index_criteria(array('real_field' => $real_name, 'value' => $value, 'field' => $field)); 1079 return $entity->delete($criteria, $action); 1080 } 1081 1082 /** 1083 * Run the delete method of all instanced class 1084 * 1085 * @return The delete for each entity 1086 */ 1087 function run_delete() 1088 { 1089 foreach($this->entities as $entity) 1090 { 1091 array_push($this->sql_deletes, $entity->delete()); 1092 } 1093 return $this->sql_deletes; 1094 } 1095 1096 function request($data) 1097 { 1098 //FIMXE: 1099 $this->operation = 'select'; 1100 if(is_string($data)) 1101 { 1102 $fields = explode(',', $data); 1103 } 1104 elseif(is_array($data)) 1105 { 1106 $fields = &$data; 1107 } 1108 foreach($fields as $field) 1109 { 1110 $this->ldebug('request', array('Data' => $field)); 1111 $this->_request($field); 1112 } 1113 } 1114 1115 /** 1116 * Find the class an call it, giving to her the field name 1117 * 1118 * @param string $data The $data that is requested 1119 */ 1120 function _request($data) 1121 { 1122 /** 1123 * This is the NEW parent/child fields 1124 * implementation, I hope this solve my 1125 * problems, but this don't remove that exist a 1126 * design problem with the n-n relations and 1127 * 1-1. 1128 */ 1129 if (is_array($this->map[$data][PHPGW_SQL_CHILD_FIELDS]) && count($this->map[$data][PHPGW_SQL_CHILD_FIELDS]) > 0) 1130 { 1131 foreach($this->map[$data][PHPGW_SQL_CHILD_FIELDS] as $child) 1132 { 1133 $this->_request($child); 1134 } 1135 } 1136 1137 $ent = $this->get_entity($data); 1138 $this->ldebug('_request', array('Data' => $data, 1139 'Entity name' => $ent)); 1140 if(is_object($this->entities[$ent])) 1141 { 1142 $this->entities[$ent]->add_select($data); 1143 } 1144 else 1145 { 1146 $this->raise_error($data); 1147 } 1148 if(intval($this->map[$data][PHPGW_SQL_CHANGE_DISTANCE]) != 0) 1149 { 1150 $this->distance[$ent] = $this->map[$data][PHPGW_SQL_CHANGE_DISTANCE]; 1151 } 1152 } 1153 1154 /** 1155 * Add external select fields to actual list 1156 * 1157 * The string is added just before the SELECT clause be created. 1158 * @param string $select actual string with select part. 1159 */ 1160 function append_select($select) 1161 { 1162 $this->external_select_value = $select; 1163 } 1164 1165 /** 1166 * Add external JOINS to actual FROM part 1167 * 1168 * String is added just before FROM clause be created. 1169 * @param string $from actual string with select part. 1170 */ 1171 function append_from($from) 1172 { 1173 $this->external_from_value = $from; 1174 } 1175 1176 /** 1177 * Add external criterias to actual query 1178 * 1179 * Run just before WHERE clause be created. 1180 * @param Array $criteria format: (token, string) where token is a conjunction class method 1181 * @see or__ 1182 * @see and__ 1183 */ 1184 function append_criteria($criteria) 1185 { 1186 $this->external_criteria_value = $criteria; 1187 } 1188 1189 /** 1190 * Allow add a link on the fly, usefull when you need add a link to a entity that is not part of your application db. 1191 * 1192 * @param string $local_entity this is the name of the entity on application db that external entity want to join. 1193 * @param string $local_field false field of $local_entity to make the join 1194 * @param string $external_entity name of entity that want to add to map for one query 1195 * @param string $external_field false field name from $enternal_entity that will be used for join 1196 * @param integer $key_type PHPGW_SQL_LAZY_KEY or PHPGW_SQL_REQUIRED_KEY. 1197 * @see set_ilinks 1198 * @see set_elinks 1199 */ 1200 function add_link($local_entity, $local_field, $external_entity, $external_field, $key_type=PHPGW_SQL_LAZY_KEY) 1201 { 1202 $local_entity = $this->get_entity($local_field); 1203 $this->entities[$local_entity]->set_elinks($local_field, $external_entity, $external_field); 1204 $external_entity = $this->get_entity($external_field); 1205 $this->entities[$external_entity]->set_ilinks($external_field, $local_entity, $local_field, $key_type); 1206 } 1207 1208 /** 1209 * Must raise errors for this class. 1210 * 1211 * @param $data From where error happens 1212 */ 1213 function raise_error($data = '') 1214 { 1215 } 1216 1217 function abort($data = '') 1218 { 1219 $GLOBALS['phpgw']->common->phpgw_exit(); 1220 } 1221 1222 function ldebug($myfoo, $data, $type = 'string', $err = '') 1223 { 1224// if (!((($myfoo != '') xor 1225// ($myfoo != '')) xor 1226// ($myfoo == '')) xor 1227// ($myfoo == 'get_sql')) 1228// if ($myfoo != 'SQL') 1229// { 1230 return; 1231// } 1232 1233 $classname = '<strong>Class: '.get_class($this). 1234 "\n<br />Function: $myfoo\n<br /></strong>"; 1235 switch($type) 1236 { 1237 case 'string': 1238 foreach($data as $vari => $value) 1239 { 1240 if (is_array($value)) 1241 { 1242 $this->ldebug($myfoo.' recursivecall', 1243 array(' -$vari: ' => $value), 'dump'); 1244 } 1245 else 1246 { 1247 $output .= " -$vari = $value \n<br />"; 1248 } 1249 } 1250 break; 1251 case 'dump': 1252 foreach($data as $vari => $value) 1253 { 1254 $output .= " -$vari = "; 1255 $output .= var_dump($value)."\n<br />"; 1256 //$output .= var_export($value, True)."\n<br />"; 1257 } 1258 break; 1259 default: 1260 $output .= "\n<br />$data\n<br />"; 1261 } 1262 if ($err != '') 1263 { 1264 $output = $classname.'Error: '.$output."\n<br />"; 1265 } 1266 else 1267 { 1268 $output = $classname.$output."\n<br />"; 1269 } 1270 echo $output; 1271 } 1272 } 1273?> 1274