1<?php 2require_once('class.ActiveRecordList.php'); 3require_once('Connector/class.arConnector.php'); 4require_once('Connector/class.arConnectorDB.php'); 5require_once('Cache/class.arObjectCache.php'); 6require_once('Fields/class.arFieldList.php'); 7require_once('Cache/class.arFieldCache.php'); 8require_once('Storage/int.arStorageInterface.php'); 9require_once('Factory/class.arFactory.php'); 10require_once('Cache/class.arCalledClassCache.php'); 11require_once('Connector/class.arConnectorMap.php'); 12 13/** 14 * Class ActiveRecord 15 * 16 * @author Fabian Schmid <fs@studer-raimann.ch> 17 * @author Oskar Truffer <ot@studer-raimann.ch> 18 * @experimental 19 * @description 20 * 21 * @version 2.0.7 22 * 23 */ 24abstract class ActiveRecord implements arStorageInterface 25{ 26 const ACTIVE_RECORD_VERSION = '2.0.7'; 27 /** 28 * @var bool 29 */ 30 protected $ar_safe_read = true; 31 /** 32 * @var string 33 */ 34 protected $connector_container_name = ''; 35 36 37 /** 38 * @return \arConnectorDB 39 */ 40 public function getArConnector() 41 { 42 return arConnectorMap::get($this); 43 } 44 45 46 /** 47 * @return \arFieldList 48 */ 49 public function getArFieldList() 50 { 51 return arFieldCache::get($this); 52 } 53 54 55 /** 56 * @throws \arException 57 * @deprecated 58 */ 59 public static function returnDbTableName() 60 { 61 throw new arException(arException::UNKNONWN_EXCEPTION, 'Implement getConnectorContainerName in your child-class'); 62 } 63 64 65 /** 66 * @return string 67 * @description Return the Name of your Connector Table 68 */ 69 public function getConnectorContainerName() 70 { 71 // WILL BE ABSTRACT TO REPLACE returnDbTableName() IN NEXT VERSION 72 if ($this->connector_container_name) { 73 return $this->connector_container_name; 74 } else { 75 $ar = self::getCalledClass(); 76 77 return $ar::returnDbTableName(); 78 } 79 } 80 81 82 /** 83 * @param string $connector_container_name 84 */ 85 public function setConnectorContainerName($connector_container_name) 86 { 87 $this->connector_container_name = $connector_container_name; 88 } 89 90 91 /** 92 * @return mixed 93 */ 94 public function getPrimaryFieldValue() 95 { 96 $primary_fieldname = arFieldCache::getPrimaryFieldName($this); 97 98 return $this->{$primary_fieldname}; 99 } 100 101 102 /** 103 * @param $value 104 */ 105 public function setPrimaryFieldValue($value) 106 { 107 $primary_fieldname = arFieldCache::getPrimaryFieldName($this); 108 109 $this->{$primary_fieldname} = $value; 110 } 111 112 113 /** 114 * @param int $primary_key 115 * @param arConnector $connector 116 */ 117 public function __construct($primary_key = 0, arConnector $connector = null) 118 { 119 // if ($connector == null) { 120 // $connector = new arConnectorDB(); 121 // } 122 // arConnectorMap::register($this, $connector); 123 124 $arFieldList = arFieldCache::get($this); 125 126 $key = $arFieldList->getPrimaryFieldName(); 127 $this->{$key} = $primary_key; 128 if ($primary_key !== 0 and $primary_key !== null and $primary_key !== false) { 129 $this->read(); 130 } 131 } 132 133 134 public function storeObjectToCache() 135 { 136 arObjectCache::store($this); 137 } 138 139 140 /** 141 * @param string $format 142 * 143 * @return array 144 */ 145 public function __getConvertedDateFieldsAsArray($format = null) 146 { 147 $converted_dates = array(); 148 foreach ($this->getArFieldList()->getFields() as $field) { 149 if ($field->isDateField()) { 150 $name = $field->getName(); 151 $value = $this->{$name}; 152 $converted_dates[$name] = array( 153 'unformatted' => $value, 154 'unix' => strtotime($value), 155 ); 156 if ($format) { 157 $converted_dates[$name]['formatted'] = date($format, strtotime($value)); 158 } 159 } 160 } 161 162 return $converted_dates; 163 } 164 165 166 /** 167 * @param string $separator 168 * @param bool $header 169 * 170 * @return string 171 */ 172 public function __asCsv($separator = ';', $header = false) 173 { 174 $line = ''; 175 if ($header) { 176 $line .= implode($separator, array_keys($this->getArFieldList()->getRawFields())); 177 $line .= "\n"; 178 } 179 $array = array(); 180 foreach ($this->__asArray() as $field_name => $value) { 181 $serialized = $this->serializeToCSV($field_name); 182 if ($serialized === null) { 183 $array[$field_name] = $this->{$field_name}; 184 } else { 185 $array[$field_name] = $serialized; 186 } 187 } 188 $line .= implode($separator, array_values($array)); 189 190 return $line; 191 } 192 193 194 /** 195 * This method is called for every field of your instance if you use __asCsv. 196 * You can use it to customize your export into csv. (e.g. serialize an array). 197 * 198 * @param $field string 199 * 200 * @return mixed 201 */ 202 protected function serializeToCSV($field) 203 { 204 return null; 205 } 206 207 208 /** 209 * @return array 210 */ 211 public function __asArray() 212 { 213 $return = array(); 214 foreach ($this->getArFieldList()->getFields() as $field) { 215 $fieldname = $field->getName(); 216 $return[$fieldname] = $this->{$fieldname}; 217 } 218 219 return $return; 220 } 221 222 223 /** 224 * @return stdClass 225 */ 226 public function __asStdClass() 227 { 228 $return = new stdClass(); 229 foreach ($this->getArFieldList()->getFields() as $field) { 230 $fieldname = $field->getName(); 231 $return->{$fieldname} = $this->{$fieldname}; 232 } 233 234 return $return; 235 } 236 237 238 /** 239 * @return string 240 */ 241 public function __asSerializedObject() 242 { 243 return serialize($this); 244 } 245 246 247 /** 248 * @param array $array 249 * 250 * @return $this 251 */ 252 public function buildFromArray(array $array) 253 { 254 $class = get_class($this); 255 $primary = $this->getArFieldList()->getPrimaryFieldName(); 256 $primary_value = $array[$primary]; 257 if ($primary_value and arObjectCache::isCached($class, $primary_value)) { 258 return arObjectCache::get($class, $primary_value); 259 } 260 foreach ($array as $field_name => $value) { 261 $waked = $this->wakeUp($field_name, $value); 262 $this->{$field_name} = ($waked === null) ? $value : $waked; 263 } 264 arObjectCache::store($this); 265 $this->afterObjectLoad(); 266 267 return $this; 268 } 269 270 271 /** 272 * @param $field_name 273 * @param $value 274 * @return string 275 */ 276 public function fixDateField($field_name, $value) 277 { 278 if ($this->getArFieldList()->getFieldByName($field_name)->isDateField()) { 279 return $this->getArConnector()->fixDate($value); 280 } 281 282 return $value; 283 } 284 285 286 /** 287 * @param $field_name 288 * 289 * @return mixed 290 */ 291 public function sleep($field_name) 292 { 293 return null; 294 } 295 296 297 /** 298 * @param $field_name 299 * @param $field_value 300 * 301 * @return mixed 302 */ 303 public function wakeUp($field_name, $field_value) 304 { 305 return null; 306 } 307 308 309 /** 310 * @return array 311 * @deprecated 312 */ 313 final public function getArrayForDb() 314 { 315 return $this->getArrayForConnector(); 316 } 317 318 319 /** 320 * @return array 321 */ 322 final public function getArrayForConnector() 323 { 324 $data = array(); 325 foreach ($this->getArFieldList()->getFields() as $field) { 326 $field_name = $field->getName(); 327 $sleeped = $this->sleep($field_name); 328 $var = ($sleeped === null) ? ($this->{$field_name}) : $sleeped; 329 $data[$field_name] = array( $field->getFieldType(), $var ); 330 } 331 332 return $data; 333 } 334 335 336 337 338 // 339 // Collector Modifications 340 // 341 342 /** 343 * @return ActiveRecord 344 * 345 * @description Returns an instance of the instatiated calling active record (needs to be done in static methods) 346 * @TODO : This should be cached somehow 347 */ 348 protected static function getCalledClass() 349 { 350 $class = get_called_class(); 351 352 return arCalledClassCache::get($class); 353 } 354 355 356 /** 357 * @return bool 358 * 359 * @deprecated Do not use in Core DB-update. Please generate the manual installation script by using: 360 * 361 * $arBuilder = new arBuilder(new ilYourARBasedClass()); 362 * $arBuilder->generateDBUpdateForInstallation(); 363 */ 364 final public static function installDB() 365 { 366 return self::getCalledClass()->installDatabase(); 367 } 368 369 370 /** 371 * @return bool 372 * 373 * @deprecated Do not use in Core DB-update. 374 */ 375 public function installConnector() 376 { 377 return $this->installDatabase(); 378 } 379 380 381 /** 382 * @param $old_name 383 * @param $new_name 384 * 385 * @return bool 386 */ 387 final public static function renameDBField($old_name, $new_name) 388 { 389 return self::getCalledClass()->getArConnector()->renameField(self::getCalledClass(), $old_name, $new_name); 390 } 391 392 393 /** 394 * @return bool 395 */ 396 final public static function tableExists() 397 { 398 return self::getCalledClass()->getArConnector()->checkTableExists(self::getCalledClass()); 399 } 400 401 402 /** 403 * @param $field_name 404 * 405 * @return bool 406 */ 407 final public static function fieldExists($field_name) 408 { 409 return self::getCalledClass()->getArConnector()->checkFieldExists(self::getCalledClass(), $field_name); 410 } 411 412 413 /** 414 * @param $field_name 415 * 416 * @return bool 417 */ 418 final public static function removeDBField($field_name) 419 { 420 return self::getCalledClass()->getArConnector()->removeField(self::getCalledClass(), $field_name); 421 } 422 423 424 /** 425 * @return bool 426 */ 427 final protected function installDatabase() 428 { 429 if (!$this->tableExists()) { 430 $fields = array(); 431 foreach ($this->getArFieldList()->getFields() as $field) { 432 $fields[$field->getName()] = $field->getAttributesForConnector(); 433 } 434 435 return $this->getArConnector()->installDatabase($this, $fields); 436 } else { 437 return $this->getArConnector()->updateDatabase($this); 438 } 439 } 440 441 442 /** 443 * @return bool 444 */ 445 final public static function updateDB() 446 { 447 if (!self::tableExists()) { 448 self::getCalledClass()->installDatabase(); 449 450 return true; 451 } 452 453 return self::getCalledClass()->getArConnector()->updateDatabase(self::getCalledClass()); 454 } 455 456 457 /** 458 * @return bool 459 */ 460 final public static function resetDB() 461 { 462 return self::getCalledClass()->getArConnector()->resetDatabase(self::getCalledClass()); 463 } 464 465 466 /** 467 * @return bool 468 */ 469 final public static function truncateDB() 470 { 471 return self::getCalledClass()->getArConnector()->truncateDatabase(self::getCalledClass()); 472 } 473 474 475 /** 476 * @return bool 477 */ 478 final public static function flushDB() 479 { 480 return self::truncateDB(); 481 } 482 483 // 484 // CRUD 485 // 486 public function store() 487 { 488 $primary_fieldname = arFieldCache::getPrimaryFieldName($this); 489 $primary_value = $this->getPrimaryFieldValue(); 490 491 if (!self::where(array( $primary_fieldname => $primary_value ))->hasSets()) { 492 $this->create(); 493 } else { 494 $this->update(); 495 } 496 } 497 498 499 public function save() 500 { 501 $this->store(); 502 } 503 504 505 public function create() 506 { 507 if ($this->getArFieldList()->getPrimaryField()->getSequence()) { 508 $primary_fieldname = arFieldCache::getPrimaryFieldName($this); 509 $this->{$primary_fieldname} = $this->getArConnector()->nextID($this); 510 } 511 512 $this->getArConnector()->create($this, $this->getArrayForConnector()); 513 arObjectCache::store($this); 514 } 515 516 517 /** 518 * @param int $new_id 519 * 520 * @return ActiveRecord 521 * @throws arException 522 */ 523 public function copy($new_id = 0) 524 { 525 if (self::where(array( $this->getArFieldList()->getPrimaryFieldName() => $new_id ))->hasSets()) { 526 throw new arException(arException::COPY_DESTINATION_ID_EXISTS); 527 } 528 $new_obj = clone($this); 529 $new_obj->setPrimaryFieldValue($new_id); 530 531 return $new_obj; 532 } 533 534 535 public function afterObjectLoad() 536 { 537 } 538 539 540 /** 541 * @throws arException 542 */ 543 public function read() 544 { 545 $records = $this->getArConnector()->read($this); 546 if (is_array($records) && count($records) === 0 && $this->ar_safe_read === true) { 547 throw new arException(arException::RECORD_NOT_FOUND, $this->getPrimaryFieldValue()); 548 } elseif (is_array($records) && count($records) === 0 && $this->ar_safe_read === false) { 549 $this->is_new = true; 550 } 551 $records = is_array($records) ? $records : array(); 552 foreach ($records as $rec) { 553 foreach ($this->getArrayForConnector() as $k => $v) { 554 $waked = $this->wakeUp($k, $rec->{$k}); 555 $this->{$k} = ($waked === null) ? $rec->{$k} : $waked; 556 } 557 arObjectCache::store($this); 558 $this->afterObjectLoad(); 559 } 560 } 561 562 563 public function update() 564 { 565 $this->getArConnector()->update($this); 566 arObjectCache::store($this); 567 } 568 569 570 public function delete() 571 { 572 $this->getArConnector()->delete($this); 573 arObjectCache::purge($this); 574 } 575 576 577 578 // 579 // Collection 580 // 581 /** 582 * @return ActiveRecord[] 583 */ 584 public static function preloadObjects() 585 { 586 return self::get(); 587 } 588 589 590 /** 591 * @param array $additional_params 592 * 593 * @return $this 594 */ 595 public static function additionalParams(array $additional_params) 596 { 597 $srModelObjectList = new ActiveRecordList(self::getCalledClass()); 598 $srModelObjectList->additionalParams($additional_params); 599 600 return $srModelObjectList; 601 } 602 603 604 /** 605 * @param $primary_key 606 * @param array $add_constructor_args 607 * 608 * @return ActiveRecord 609 */ 610 public static function find($primary_key, array $add_constructor_args = array()) 611 { 612 /** 613 * @var $obj ActiveRecord 614 */ 615 try { 616 $class_name = get_called_class(); 617 if (!arObjectCache::isCached($class_name, $primary_key)) { 618 $obj = arFactory::getInstance($class_name, $primary_key, $add_constructor_args); 619 $obj->storeObjectToCache(); 620 621 return $obj; 622 } 623 } catch (arException $e) { 624 return null; 625 } 626 627 try { 628 $obj = arObjectCache::get($class_name, $primary_key); 629 } catch (arException $e) { 630 return null; 631 } 632 633 return $obj; 634 } 635 636 637 /** 638 * Tries to find the object and throws an Exception if object is not found, instead of returning null 639 * 640 * @param $primary_key 641 * @param array $add_constructor_args 642 * @throws arException 643 * @return ActiveRecord 644 */ 645 public static function findOrFail($primary_key, array $add_constructor_args = array()) 646 { 647 $obj = self::find($primary_key, $add_constructor_args); 648 if (is_null($obj)) { 649 throw new arException(arException::RECORD_NOT_FOUND); 650 } 651 652 return $obj; 653 } 654 655 656 /** 657 * @param $primary_key 658 * @param array $add_constructor_args 659 * 660 * @description Returns an existing Object with given primary-key or a new Instance with given primary-key set but not yet created 661 * 662 * @return ActiveRecord 663 */ 664 public static function findOrGetInstance($primary_key, array $add_constructor_args = array()) 665 { 666 $obj = self::find($primary_key, $add_constructor_args); 667 if ($obj !== null) { 668 return $obj; 669 } else { 670 $class_name = get_called_class(); 671 $obj = arFactory::getInstance($class_name, 0, $add_constructor_args); 672 $obj->setPrimaryFieldValue($primary_key); 673 $obj->is_new = true; 674 $obj->storeObjectToCache(); 675 676 return $obj; 677 } 678 } 679 680 681 /** 682 * @param $where 683 * @param null $operator 684 * 685 * @return ActiveRecordList 686 */ 687 public static function where($where, $operator = null) 688 { 689 $srModelObjectList = new ActiveRecordList(self::getCalledClass()); 690 $srModelObjectList->where($where, $operator); 691 692 return $srModelObjectList; 693 } 694 695 696 /** 697 * @param ActiveRecord $ar 698 * @param $on_this 699 * @param $on_external 700 * @param array $fields 701 * @param string $operator 702 * 703 * @return $this 704 */ 705 public static function innerjoinAR(ActiveRecord $ar, $on_this, $on_external, $fields = array( '*' ), $operator = '=', $both_external = false) 706 { 707 return self::innerjoin($ar->getConnectorContainerName(), $on_this, $on_external, $fields, $operator, $both_external); 708 } 709 710 711 /** 712 * @param $tablename 713 * @param $on_this 714 * @param $on_external 715 * @param array $fields 716 * @param string $operator 717 * 718 * @return $this 719 */ 720 public static function innerjoin($tablename, $on_this, $on_external, $fields = array( '*' ), $operator = '=', $both_external = false) 721 { 722 $srModelObjectList = new ActiveRecordList(self::getCalledClass()); 723 724 return $srModelObjectList->innerjoin($tablename, $on_this, $on_external, $fields, $operator, $both_external); 725 } 726 727 728 /** 729 * @param $tablename 730 * @param $on_this 731 * @param $on_external 732 * @param array $fields 733 * @param string $operator 734 * 735 * @return $this 736 */ 737 public static function leftjoin($tablename, $on_this, $on_external, $fields = array( '*' ), $operator = '=', $both_external = false) 738 { 739 $srModelObjectList = new ActiveRecordList(self::getCalledClass()); 740 741 return $srModelObjectList->leftjoin($tablename, $on_this, $on_external, $fields, $operator, $both_external); 742 } 743 744 745 /** 746 * @param $orderBy 747 * @param string $orderDirection 748 * 749 * @return ActiveRecordList 750 */ 751 public static function orderBy($orderBy, $orderDirection = 'ASC') 752 { 753 $srModelObjectList = new ActiveRecordList(self::getCalledClass()); 754 $srModelObjectList->orderBy($orderBy, $orderDirection); 755 756 return $srModelObjectList; 757 } 758 759 760 /** 761 * @param string $date_format 762 * 763 * @return ActiveRecordList 764 */ 765 public static function dateFormat($date_format = 'd.m.Y - H:i:s') 766 { 767 $srModelObjectList = new ActiveRecordList(self::getCalledClass()); 768 $srModelObjectList->dateFormat($date_format); 769 770 return $srModelObjectList; 771 } 772 773 774 /** 775 * @param $start 776 * @param $end 777 * 778 * @return ActiveRecordList 779 */ 780 public static function limit($start, $end) 781 { 782 $srModelObjectList = new ActiveRecordList(self::getCalledClass()); 783 $srModelObjectList->limit($start, $end); 784 785 return $srModelObjectList; 786 } 787 788 789 /** 790 * @return int 791 */ 792 public static function affectedRows() 793 { 794 $srModelObjectList = new ActiveRecordList(self::getCalledClass()); 795 796 return $srModelObjectList->affectedRows(); 797 } 798 799 800 /** 801 * @return int 802 */ 803 public static function count() 804 { 805 return self::affectedRows(); 806 } 807 808 809 /** 810 * @return ActiveRecord[] 811 */ 812 public static function get() 813 { 814 $srModelObjectList = new ActiveRecordList(self::getCalledClass()); 815 816 return $srModelObjectList->get(); 817 } 818 819 820 /** 821 * @return ActiveRecordList 822 */ 823 public static function debug() 824 { 825 $srModelObjectList = new ActiveRecordList(self::getCalledClass()); 826 827 return $srModelObjectList->debug(); 828 } 829 830 831 /** 832 * @return ActiveRecord 833 */ 834 public static function first() 835 { 836 $srModelObjectList = new ActiveRecordList(self::getCalledClass()); 837 838 return $srModelObjectList->first(); 839 } 840 841 842 /** 843 * @return ActiveRecordList 844 */ 845 public static function getCollection() 846 { 847 $srModelObjectList = new ActiveRecordList(self::getCalledClass()); 848 849 return $srModelObjectList; 850 } 851 852 853 /** 854 * @return ActiveRecord 855 */ 856 public static function last() 857 { 858 $srModelObjectList = new ActiveRecordList(self::getCalledClass()); 859 860 return $srModelObjectList->last(); 861 } 862 863 864 /** 865 * @return ActiveRecordList 866 * @deprecated 867 */ 868 public static function getFirstFromLastQuery() 869 { 870 $srModelObjectList = new ActiveRecordList(self::getCalledClass()); 871 872 return $srModelObjectList->getFirstFromLastQuery(); 873 } 874 875 876 /** 877 * @param arConnector $connector 878 * 879 * @return ActiveRecordList 880 */ 881 public static function connector(arConnector $connector) 882 { 883 $srModelObjectList = new ActiveRecordList(self::getCalledClass()); 884 885 return $srModelObjectList->connector($connector); 886 } 887 888 889 /** 890 * @param bool $set_raw 891 * 892 * @return ActiveRecordList 893 */ 894 public static function raw($set_raw = true) 895 { 896 $srModelObjectList = new ActiveRecordList(self::getCalledClass()); 897 898 return $srModelObjectList->raw($set_raw); 899 } 900 901 902 /** 903 * @param null $key 904 * @param null $values 905 * 906 * @return array 907 */ 908 public static function getArray($key = null, $values = null) 909 { 910 $srModelObjectList = new ActiveRecordList(self::getCalledClass()); 911 912 return $srModelObjectList->getArray($key, $values); 913 } 914 915 // 916 // Magic Methods & Helpers 917 // 918 /** 919 * @param $name 920 * @param $arguments 921 * 922 * @return array 923 */ 924 public function __call($name, $arguments) 925 { 926 // Getter 927 if (preg_match("/get([a-zA-Z]*)/u", $name, $matches) and count($arguments) == 0) { 928 return $this->{self::fromCamelCase($matches[1])}; 929 } 930 // Setter 931 if (preg_match("/set([a-zA-Z]*)/u", $name, $matches) and count($arguments) == 1) { 932 $this->{self::fromCamelCase($matches[1])} = $arguments[0]; 933 } 934 if (preg_match("/findBy([a-zA-Z]*)/u", $name, $matches) and count($arguments) == 1) { 935 return self::where(array( self::fromCamelCase($matches[1]) => $arguments[0] ))->getFirst(); 936 } 937 } 938 939 940 /** 941 * @param string $str 942 * @param bool $capitalise_first_char 943 * 944 * @return string 945 */ 946 public static function _toCamelCase($str, $capitalise_first_char = false) 947 { 948 if ($capitalise_first_char) { 949 $str[0] = strtoupper($str[0]); 950 } 951 952 return preg_replace_callback('/_([a-z])/', function ($c) { 953 return strtoupper($c[1]); 954 }, $str); 955 } 956 957 958 /** 959 * @param string $str 960 * 961 * @return string 962 */ 963 protected static function fromCamelCase($str) 964 { 965 $str[0] = strtolower($str[0]); 966 967 return preg_replace_callback('/([A-Z])/', function ($c) { 968 return "_" . strtolower($c[1]); 969 }, $str); 970 } 971} 972