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