1<?php
2/* Copyright (c) 1998-2009 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4/**
5 * ADT based-object base class
6 *
7 * Currently "mixed" with ActiveRecord-pattern, could be splitted
8 *
9 * @author Jörg Lützenkirchen <luetzenkirchen@leifos.com>
10 * @version $Id$
11 * @ingroup ServicesADT
12 */
13abstract class ilADTBasedObject
14{
15    protected $properties = array(); // [array]
16    protected $db_errors = array(); // [array]
17
18    /**
19     * Constructor
20     *
21     * Tries to read record from DB, in accordance to current ILIAS behaviour
22     *
23     * @return self
24     */
25    public function __construct()
26    {
27        $this->properties = $this->initProperties();
28
29        // :TODO: to keep constructor "open" we COULD use func_get_args()
30        $this->parsePrimary(func_get_args());
31        $this->read();
32    }
33
34
35    //
36    // properties
37    //
38
39    /**
40     * Init properties (aka set ADT definition)
41     *
42     * @return ilADT
43     */
44    abstract protected function initProperties();
45
46    /**
47     * Get all properties
48     *
49     * @return array ilADT
50     */
51    public function getProperties()
52    {
53        return $this->properties;
54    }
55
56    /**
57     * Validate
58     *
59     * @return bool
60     */
61    public function isValid()
62    {
63        return $this->properties->isValid();
64    }
65
66    /**
67     * Get property magic method ("get<PropertyName>()")
68     *
69     * Setters are type-specific and cannot be magic
70     *
71     * @throws Exception
72     * @param string $a_method
73     * @param mixed $a_value
74     * @return ilADT
75     */
76    public function __call($a_method, $a_value)
77    {
78        $type = substr($a_method, 0, 3);
79        switch ($type) {
80            case "get":
81                $parsed = strtolower(preg_replace("/([A-Z])/", " $1", substr($a_method, 3)));
82                $parsed = str_replace(" ", "_", trim($parsed));
83                if (!$this->properties->hasElement($parsed)) {
84                    throw new Exception("ilADTObject unknown property " . $parsed);
85                }
86                return $this->properties->getElement($parsed);
87
88            default:
89                throw new Exception("ilADTObject unknown method " . $parsed);
90        }
91    }
92
93
94    //
95    // CRUD / active record
96    //
97
98    /**
99     * Parse incoming primary key
100     *
101     * @see __construct()
102     * @param array $a_args
103     */
104    abstract protected function parsePrimary(array $a_args);
105
106    /**
107     * Check if currently has primary
108     *
109     * @return bool
110     */
111    abstract protected function hasPrimary();
112
113    /**
114     * Create new primary key, e.g. sequence
115     *
116     * @return bool
117     */
118    abstract protected function createPrimaryKey();
119
120    /**
121     * Init (properties) DB bridge
122     *
123     * @param ilADTGroupDBBridge $a_adt_db
124     */
125    abstract protected function initDBBridge(ilADTGroupDBBridge $a_adt_db);
126
127    /**
128     * Init active record helper for current table, primary and properties
129     *
130     * @return ilADTActiveRecord
131     */
132    protected function initActiveRecordInstance()
133    {
134        global $DIC;
135
136        $ilDB = $DIC['ilDB'];
137
138        if (!$this->hasPrimary()) {
139            throw new Exception("ilADTBasedObject no primary");
140        }
141
142        $factory = ilADTFactory::getInstance();
143        $this->adt_db = $factory->getDBBridgeForInstance($this->properties);
144        $this->initDBBridge($this->adt_db);
145
146        // use custom error handling
147        include_once "Services/ADT/classes/class.ilADTDBException.php";
148        $ilDB->exception = "ilADTDBException";
149
150        return $factory->getActiveRecordInstance($this->adt_db);
151    }
152
153    /**
154     * Read record
155     *
156     * @return boolean
157     */
158    public function read()
159    {
160        if ($this->hasPrimary()) {
161            $rec = $this->initActiveRecordInstance();
162            return $rec->read();
163        }
164        return false;
165    }
166
167    /**
168     * Create record (only if valid)
169     *
170     * @return boolean
171     */
172    public function create()
173    {
174        if ($this->hasPrimary()) {
175            return $this->update();
176        }
177
178        if ($this->isValid()) {
179            if ($this->createPrimaryKey()) {
180                try {
181                    $rec = $this->initActiveRecordInstance();
182                    $rec->create();
183                } catch (ilADTDBException $e) {
184                    $this->db_errors[$e->getColumn()][] = $e->getCode();
185                    return false;
186                }
187                return true;
188            }
189        }
190        return false;
191    }
192
193    /**
194     * Update record (only if valid)
195     *
196     * @return boolean
197     */
198    public function update()
199    {
200        if (!$this->hasPrimary()) {
201            return $this->create();
202        }
203
204        if ($this->isValid()) {
205            try {
206                $rec = $this->initActiveRecordInstance();
207                $rec->update();
208            } catch (ilADTDBException $e) {
209                $this->db_errors[$e->getColumn()][] = $e->getCode();
210                return false;
211            }
212            return true;
213        }
214        return false;
215    }
216
217    /**
218     * Delete record
219     *
220     * @return boolean
221     */
222    public function delete()
223    {
224        if ($this->hasPrimary()) {
225            $rec = $this->initActiveRecordInstance();
226            $rec->delete();
227            return true;
228        }
229        return false;
230    }
231
232    /**
233     * Get DB errors
234     *
235     * @return array
236     */
237    public function getDBErrors()
238    {
239        return $this->db_errors;
240    }
241
242    /**
243     * Translate DB error codes
244     *
245     * @param array $a_codes
246     * @return array
247     */
248    public function translateDBErrorCodes(array $a_codes)
249    {
250        global $DIC;
251
252        $lng = $DIC['lng'];
253
254        $res = array();
255
256        foreach ($a_codes as $code) {
257            switch ($code) {
258                case MDB2_ERROR_CONSTRAINT:
259                    $res[] = $lng->txt("adt_error_db_constraint");
260                    break;
261
262                default:
263                    $res[] = "Unknown ADT error code " . $code;
264                    break;
265            }
266        }
267
268        return $res;
269    }
270
271    /**
272     * Get translated error codes (DB, Validation)
273     *
274     * @param type $delimiter
275     * @return string
276     */
277    public function getAllTranslatedErrors($delimiter = "\n")
278    {
279        $tmp = array();
280
281        foreach ($this->getProperties()->getValidationErrorsByElements() as $error_code => $element_id) {
282            $tmp[] = $element_id . " [validation]: " . $this->getProperties()->translateErrorCode($error_code);
283        }
284
285        foreach ($this->getDBErrors() as $element_id => $codes) {
286            $tmp[] = $element_id . " [db]: " . implode($delimiter, $this->translateDBErrorCodes($codes));
287        }
288
289        if (sizeof($tmp)) {
290            return get_class($this) . $delimiter . implode($delimiter, $tmp);
291        }
292    }
293}
294