1 <?php
  2 
  3 define('REX_FORM_ERROR_VIOLATE_UNIQUE_KEY', 1062);
  4 
  5 /**
  6  * rex_form repraesentiert ein Formular in REDAXO.
  7  * Diese Klasse kann in Frontend u. Backend eingesetzt werden.
  8  *
  9  * Nach erzeugen eines Formulars mit der factory()-Methode muss dieses mit verschiedenen Input-Feldern bestueckt werden.
 10  * Dies geschieht Mittels der add*Field(...) Methoden.
 11  *
 12  * Nachdem alle Felder eingefuegt wurden, muss das Fomular mit get() oder show() ausgegeben werden.
 13  *
 14  * @package redaxo\core\form
 15  */
 16 class rex_form extends rex_form_base
 17 {
 18     use rex_factory_trait;
 19 
 20     protected $tableName;
 21     protected $whereCondition;
 22     protected $mode;
 23     protected $db;
 24     protected $sql;
 25     protected $languageSupport;
 26 
 27     /**
 28      * Diese Konstruktor sollte nicht verwendet werden. Instanzen muessen ueber die facotry() Methode erstellt werden!
 29      */
 30     protected function __construct($tableName, $fieldset, $whereCondition, $method = 'post', $debug = false, $db = 1)
 31     {
 32         $name = md5($tableName . $whereCondition . $method);
 33 
 34         parent::__construct($fieldset, $name, $method, $debug);
 35 
 36         $this->tableName = $tableName;
 37         $this->whereCondition = $whereCondition;
 38         $this->languageSupport = [];
 39 
 40         $this->db = $db;
 41         $this->sql = rex_sql::factory($db);
 42         $this->sql->setDebug($this->debug);
 43         $this->sql->setQuery('SELECT * FROM ' . $tableName . ' WHERE ' . $this->whereCondition . ' LIMIT 2');
 44 
 45         $this->setFormId('rex-addon-editmode');
 46 
 47         // --------- validate where-condition and determine editMode
 48         $numRows = $this->sql->getRows();
 49         if ($numRows == 0) {
 50             // Kein Datensatz gefunden => Mode: Add
 51             $this->setEditMode(false);
 52         } elseif ($numRows == 1) {
 53             // Ein Datensatz gefunden => Mode: Edit
 54             $this->setEditMode(true);
 55         } else {
 56             throw new rex_exception('rex_form: Die gegebene Where-Bedingung führt nicht zu einem eindeutigen Datensatz!');
 57         }
 58 
 59         // --------- Load Env
 60         if (rex::isBackend()) {
 61             $this->loadBackendConfig();
 62         }
 63     }
 64 
 65     /**
 66      * Methode zum erstellen von rex_form Instanzen.
 67      *
 68      * @param string $tableName
 69      * @param string $fieldset
 70      * @param string $whereCondition
 71      * @param string $method
 72      * @param bool   $debug
 73      * @param int    $db             DB connection ID
 74      *
 75      * @return static a rex_form instance
 76      */
 77     public static function factory($tableName, $fieldset, $whereCondition, $method = 'post', $debug = false, $db = 1)
 78     {
 79         $class = static::getFactoryClass();
 80         return new $class($tableName, $fieldset, $whereCondition, $method, $debug, $db);
 81     }
 82 
 83     /**
 84      * Laedt die Konfiguration die noetig ist um rex_form im REDAXO Backend zu verwenden.
 85      */
 86     protected function loadBackendConfig()
 87     {
 88         parent::loadBackendConfig();
 89 
 90         $func = rex_request('func', 'string');
 91 
 92         $this->addParam('func', $func);
 93         $this->addParam('list', rex_request('list', 'string'));
 94 
 95         $controlFields = [];
 96         $controlFields['save'] = rex_i18n::msg('form_save');
 97         $controlFields['apply'] = $func == 'edit' ? rex_i18n::msg('form_apply') : '';
 98         $controlFields['delete'] = $func == 'edit' ? rex_i18n::msg('form_delete') : '';
 99         $controlFields['reset'] = ''; //rex_i18n::msg('form_reset');
100         $controlFields['abort'] = rex_i18n::msg('form_abort');
101 
102         // ----- EXTENSION POINT
103         $controlFields = rex_extension::registerPoint(new rex_extension_point('REX_FORM_CONTROL_FIELDS', $controlFields, ['form' => $this]));
104 
105         $controlElements = [];
106         foreach ($controlFields as $name => $label) {
107             if ($label) {
108                 $attr = ['type' => 'submit', 'internal::useArraySyntax' => false, 'internal::fieldSeparateEnding' => true];
109 
110                 if ($name === 'abort' || $name === 'delete') {
111                     $attr['formnovalidate'] = 'formnovalidate';
112                 }
113                 $controlElements[$name] = $this->addField(
114                     'button',
115                     $name,
116                     $label,
117                     $attr,
118                     false
119                 );
120             } else {
121                 $controlElements[$name] = null;
122             }
123         }
124 
125         $this->addControlField(
126             $controlElements['save'],
127             $controlElements['apply'],
128             $controlElements['delete'],
129             $controlElements['reset'],
130             $controlElements['abort']
131         );
132     }
133 
134     /**
135      * Fuegt dem Formular ein Feld hinzu mitdem die Prioritaet von Datensaetzen verwaltet werden kann.
136      *
137      * @param string $name
138      * @param mixed  $value
139      * @param array  $attributes
140      *
141      * @return rex_form_prio_element
142      */
143     public function addPrioField($name, $value = null, array $attributes = [])
144     {
145         $attributes['internal::fieldClass'] = 'rex_form_prio_element';
146         if (!isset($attributes['class'])) {
147             $attributes['class'] = 'form-control';
148         }
149         $field = $this->addField('', $name, $value, $attributes, true);
150         return $field;
151     }
152 
153     /**
154      * Gibt die Where-Bedingung des Formulars zurueck.
155      */
156     public function getWhereCondition()
157     {
158         return $this->whereCondition;
159     }
160 
161     /**
162      * Mehrsprachigkeit unterstuetzen.
163      *
164      * @param string $idField
165      * @param string $clangField
166      */
167     public function setLanguageSupport($idField, $clangField)
168     {
169         $this->languageSupport['id'] = $idField;
170         $this->languageSupport['clang'] = $clangField;
171     }
172 
173     /**
174      * Wechselt den Modus des Formulars.
175      */
176     public function setEditMode($isEditMode)
177     {
178         if ($isEditMode) {
179             $this->mode = 'edit';
180         } else {
181             $this->mode = 'add';
182         }
183     }
184 
185     /**
186      * Prueft ob sich das Formular im Edit-Modus befindet.
187      *
188      * @return bool
189      */
190     public function isEditMode()
191     {
192         return $this->mode == 'edit';
193     }
194 
195     /**
196      * @return string
197      */
198     public function getTableName()
199     {
200         return $this->tableName;
201     }
202 
203     /**
204      * @return rex_sql
205      */
206     public function getSql()
207     {
208         return $this->sql;
209     }
210 
211     protected function getId($name)
212     {
213         return $this->tableName . '_' . $this->fieldset . '_' . $name;
214     }
215 
216     protected function getValue($name)
217     {
218         if ($this->sql->getRows() == 1 && $this->sql->hasValue($name)) {
219             return $this->sql->getValue($name);
220         }
221 
222         return null;
223     }
224 
225     /**
226      * Callbackfunktion, damit in subklassen der Value noch beeinflusst werden kann
227      * kurz vorm speichern.
228      */
229     protected function preSave($fieldsetName, $fieldName, $fieldValue, rex_sql $saveSql)
230     {
231         static $setOnce = false;
232 
233         if (!$setOnce) {
234             $fieldnames = $this->sql->getFieldnames();
235 
236             if (in_array('updateuser', $fieldnames)) {
237                 $saveSql->setValue('updateuser', rex::getUser()->getValue('login'));
238             }
239 
240             if (in_array('updatedate', $fieldnames)) {
241                 $saveSql->setDateTimeValue('updatedate', time());
242             }
243 
244             if (!$this->isEditMode()) {
245                 if (in_array('createuser', $fieldnames)) {
246                     $saveSql->setValue('createuser', rex::getUser()->getValue('login'));
247                 }
248 
249                 if (in_array('createdate', $fieldnames)) {
250                     $saveSql->setDateTimeValue('createdate', time());
251                 }
252             }
253             $setOnce = true;
254         }
255 
256         return $fieldValue;
257     }
258 
259     /**
260      * @param object $form
261      *
262      * @return bool
263      */
264     public function equals($form)
265     {
266         return
267             $form instanceof self &&
268             $this->getTableName() == $form->getTableName() &&
269             $this->getWhereCondition() == $form->getWhereCondition();
270     }
271 
272     /**
273      * Speichert das Formular.
274      *
275      * Übernimmt die Werte aus den FormElementen in die Datenbank.
276      *
277      * Gibt true zurück wenn alles ok war, false bei einem allgemeinen Fehler,
278      * einen String mit einer Fehlermeldung oder den von der Datenbank gelieferten ErrorCode.
279      *
280      * @return bool
281      */
282     protected function save()
283     {
284         $sql = rex_sql::factory($this->db);
285         $sql->setDebug($this->debug);
286         $sql->setTable($this->tableName);
287 
288         $values = [];
289         foreach ($this->getSaveElements() as $fieldsetName => $fieldsetElements) {
290             foreach ($fieldsetElements as $element) {
291                 // read-only-fields nicht speichern
292                 if (strpos($element->getAttribute('class'), 'form-control-static') !== false) {
293                     continue;
294                 }
295 
296                 $fieldName = $element->getFieldName();
297                 $fieldValue = $element->getSaveValue();
298 
299                 // Callback, um die Values vor dem Speichern noch beeinflussen zu können
300                 $fieldValue = $this->preSave($fieldsetName, $fieldName, $fieldValue, $sql);
301 
302                 if (is_string($fieldValue)) {
303                     $fieldValue = trim($fieldValue);
304                 }
305                 $values[$fieldName] = $fieldValue;
306             }
307         }
308 
309         try {
310             if ($this->isEditMode()) {
311                 $sql->setValues($values);
312                 $sql->setWhere($this->whereCondition);
313                 $sql->update();
314             } else {
315                 if (count($this->languageSupport)) {
316                     foreach (rex_clang::getAllIds() as $clang_id) {
317                         $sql->setTable($this->tableName);
318                         $sql->addGlobalCreateFields();
319                         $sql->addGlobalUpdateFields();
320                         if (!isset($id)) {
321                             $id = $sql->setNewId($this->languageSupport['id']);
322                         } else {
323                             $sql->setValue($this->languageSupport['id'], $id);
324                         }
325                         $sql->setValue($this->languageSupport['clang'], $clang_id);
326                         $sql->setValues($values);
327                         $sql->insert();
328                     }
329                 } else {
330                     $sql->setValues($values);
331                     $sql->insert();
332                 }
333             }
334             $saved = true;
335         } catch (rex_sql_exception $e) {
336             $saved = false;
337         }
338 
339         // ----- EXTENSION POINT
340         if ($saved) {
341             $saved = rex_extension::registerPoint(new rex_extension_point('REX_FORM_SAVED', $saved, ['form' => $this, 'sql' => $sql]));
342         } else {
343             $saved = $sql->getMysqlErrno();
344         }
345 
346         return $saved;
347     }
348 
349     /**
350      * @return bool
351      */
352     protected function delete()
353     {
354         $deleteSql = rex_sql::factory($this->db);
355         $deleteSql->setDebug($this->debug);
356         $deleteSql->setTable($this->tableName);
357         $deleteSql->setWhere($this->whereCondition);
358 
359         try {
360             $deleteSql->delete();
361             $deleted = true;
362         } catch (rex_sql_exception $e) {
363             $deleted = false;
364         }
365 
366         // ----- EXTENSION POINT
367         if ($deleted) {
368             $deleted = rex_extension::registerPoint(new rex_extension_point('REX_FORM_DELETED', $deleted, ['form' => $this, 'sql' => $deleteSql]));
369         } else {
370             $deleted = $deleteSql->getMysqlErrno();
371         }
372 
373         return $deleted;
374     }
375 }
376