Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
56.68% covered (warning)
56.68%
314 / 554
45.65% covered (danger)
45.65%
21 / 46
CRAP
0.00% covered (danger)
0.00%
0 / 2
SeedDMS_Core_Attribute
57.38% covered (warning)
57.38%
35 / 61
46.15% covered (danger)
46.15%
6 / 13
123.51
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 setDMS
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDMS
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getID
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getValue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __getParsedValue
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 getValueAsArray
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getValueAsString
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 setValue
66.67% covered (warning)
66.67%
20 / 30
0.00% covered (danger)
0.00%
0 / 1
25.48
 validate
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getValidationError
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setValidationError
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAttributeDefinition
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
SeedDMS_Core_AttributeDefinition
56.59% covered (warning)
56.59%
279 / 493
45.45% covered (danger)
45.45%
15 / 33
5616.19
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 setDMS
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDMS
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getID
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setName
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
2.01
 getObjType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setObjType
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
2.01
 getType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setType
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
2.01
 getMultipleValues
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setMultipleValues
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
2.01
 getMinValues
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setMinValues
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
2.01
 getMaxValues
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setMaxValues
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
2.01
 getValueSet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getValueSetSeparator
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 getValueSetAsArray
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getValueSetValue
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 setValueSet
84.62% covered (warning)
84.62%
11 / 13
0.00% covered (danger)
0.00%
0 / 1
3.03
 getRegex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setRegex
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 isUsed
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
56
 parseValue
36.36% covered (danger)
36.36%
16 / 44
0.00% covered (danger)
0.00%
0 / 1
159.32
 createValue
76.47% covered (warning)
76.47%
13 / 17
0.00% covered (danger)
0.00%
0 / 1
25.21
 getStatistics
37.93% covered (danger)
37.93%
33 / 87
0.00% covered (danger)
0.00%
0 / 1
364.36
 remove
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 getObjects
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
272
 removeValue
41.46% covered (danger)
41.46%
17 / 41
0.00% covered (danger)
0.00%
0 / 1
67.35
 updateValue
41.86% covered (danger)
41.86%
18 / 43
0.00% covered (danger)
0.00%
0 / 1
73.80
 validate
82.86% covered (warning)
82.86%
87 / 105
0.00% covered (danger)
0.00%
0 / 1
105.10
 getValidationError
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2declare(strict_types=1);
3
4/**
5 * Implementation of the attribute object in the document management system
6 *
7 * @category   DMS
8 * @package    SeedDMS_Core
9 * @license    GPL 2
10 * @version    @version@
11 * @author     Uwe Steinmann <uwe@steinmann.cx>
12 * @copyright  Copyright (C) 2012-2024 Uwe Steinmann
13 * @version    Release: @package_version@
14 */
15
16/**
17 * Class to represent an attribute in the document management system
18 *
19 * Attributes are key/value pairs which can be attachted to documents,
20 * folders and document content. The number of attributes is unlimited.
21 * Each attribute has a value and is related to an attribute definition,
22 * which holds the name and other information about the attribute.
23 *
24 * @see SeedDMS_Core_AttributeDefinition
25 *
26 * @category   DMS
27 * @package    SeedDMS_Core
28 * @author     Uwe Steinmann <uwe@steinmann.cx>
29 * @copyright  Copyright (C) 2012-2024 Uwe Steinmann
30 * @version    Release: @package_version@
31 */
32class SeedDMS_Core_Attribute { /* {{{ */
33    /**
34     * @var integer id of attribute
35     *
36     * @access protected
37     */
38    protected $_id;
39
40    /**
41     * @var SeedDMS_Core_Folder|SeedDMS_Core_Document|SeedDMS_Core_DocumentContent SeedDMS_Core_Object folder, document or document content
42     * this attribute belongs to
43     *
44     * @access protected
45     */
46    protected $_obj;
47
48    /**
49     * @var SeedDMS_Core_AttributeDefinition definition of this attribute
50     *
51     * @access protected
52     */
53    protected $_attrdef;
54
55    /**
56     * @var mixed value of this attribute
57     *
58     * @access protected
59     */
60    protected $_value;
61
62    /**
63     * @var integer validation error
64     *
65     * @access protected
66     */
67    protected $_validation_error;
68
69    /**
70     * @var SeedDMS_Core_DMS reference to the dms instance this attribute belongs to
71     *
72     * @access protected
73     */
74    protected $_dms;
75
76    /**
77     * SeedDMS_Core_Attribute constructor.
78     * @param $id
79     * @param $obj
80     * @param $attrdef
81     * @param $value
82     */
83    public function __construct($id, $obj, $attrdef, $value) { /* {{{ */
84        $this->_id = $id;
85        $this->_obj = $obj;
86        $this->_attrdef = $attrdef;
87        $this->_value = $value;
88        $this->_validation_error = 0;
89        $this->_dms = null;
90    } /* }}} */
91
92    /**
93     * Set reference to dms
94     *
95     * @param SeedDMS_Core_DMS $dms
96     */
97    public function setDMS($dms) { /* {{{ */
98        $this->_dms = $dms;
99    } /* }}} */
100
101    /**
102     * Get dms of attribute
103     *
104     * @return object $dms
105     */
106    public function getDMS() { return $this->_dms; }
107
108    /**
109     * Get internal id of attribute
110     *
111     * @return integer id
112     */
113    public function getID() { return $this->_id; }
114
115    /**
116     * Return attribute value as stored in database
117     *
118     * This function will return the value of multi value attributes
119     * including the separator char.
120     *
121     * @return string the attribute value as it is stored in the database.
122     */
123    public function getValue() { return $this->_value; }
124
125    /**
126     * Return attribute value parsed into a php type or object
127     *
128     * This function will return the value of multi value attributes
129     * including the separator char.
130     *
131     * DEPRECATED
132     *
133     * @return string the attribute value as it is stored in the database.
134     */
135    public function __getParsedValue() { /* {{{ */
136        switch ($this->_attrdef->getType()) {
137            case SeedDMS_Core_AttributeDefinition::type_float:
138                return (float) $this->_value;
139                break;
140            case SeedDMS_Core_AttributeDefinition::type_boolean:
141                return (bool) $this->_value;
142                break;
143            case SeedDMS_Core_AttributeDefinition::type_int:
144                return (int) $this->_value;
145                break;
146            default:
147                return $this->_value;
148        }
149    } /* }}} */
150
151    /**
152     * Return attribute values as an array
153     *
154     * This function returns the attribute value as an array. The array
155     * has one element for non multi value attributes and n elements for
156     * multi value attributes.
157     *
158     * @return array the attribute values
159     */
160    public function getValueAsArray() { /* {{{ */
161        if (is_array($this->_value)) {
162            return $this->_value;
163        } else {
164            return [$this->_value];
165        }
166    } /* }}} */
167
168    public function getValueAsString() { /* {{{ */
169        if (is_array($this->_value)) {
170            return implode(', ', $this->_value);
171        } else {
172            return (string) $this->_value;
173        }
174    } /* }}} */
175
176    /**
177     * Set a value of an attribute
178     *
179     * The attribute is completely deleted if the value is an empty string
180     * or empty array. An array of values is only allowed if the attribute may
181     * have multiple values. If an array is passed and the attribute may
182     * have only a single value, then the first element of the array will
183     * be taken.
184     *
185     * @param string $values value as string or array to be set
186     * @return boolean true if operation was successfull, otherwise false
187     */
188    public function setValue($values) { /* {{{*/
189        $db = $this->_dms->getDB();
190
191        /* if $values is an array but the attribute definition does not allow
192         * multi values, then the first element of the array is taken.
193         */
194        if ($values && is_array($values) && !$this->_attrdef->getMultipleValues())
195            $values = $values[0];
196
197        /* Create a value to be stored in the database */
198        $value = $this->_attrdef->createValue($values);
199
200        switch (get_class($this->_obj)) {
201            case $this->_dms->getClassname('document'):
202                if (trim($value) === '') {
203                    $queryStr = "DELETE FROM `tblDocumentAttributes` WHERE `document` = " . $this->_obj->getID() . " AND `attrdef` = " . $this->_attrdef->getId();
204                } else {
205                    $queryStr = "UPDATE `tblDocumentAttributes` SET `value` = ".$db->qstr($value)." WHERE `document` = " . $this->_obj->getID() .    " AND `attrdef` = " . $this->_attrdef->getId();
206                }
207                break;
208            case $this->_dms->getClassname('documentcontent'):
209                if (trim($value) === '') {
210                    $queryStr = "DELETE FROM `tblDocumentContentAttributes` WHERE `content` = " . $this->_obj->getID() . " AND `attrdef` = " . $this->_attrdef->getId();
211                } else {
212                    $queryStr = "UPDATE `tblDocumentContentAttributes` SET `value` = ".$db->qstr($value)." WHERE `content` = " . $this->_obj->getID() .    " AND `attrdef` = " . $this->_attrdef->getId();
213                }
214                break;
215            case $this->_dms->getClassname('folder'):
216                if (trim($value) === '') {
217                    $queryStr = "DELETE FROM `tblFolderAttributes` WHERE `folder` = " . $this->_obj->getID() .    " AND `attrdef` = " . $this->_attrdef->getId();
218                } else {
219                    $queryStr = "UPDATE `tblFolderAttributes` SET `value` = ".$db->qstr($value)." WHERE `folder` = " . $this->_obj->getID() .    " AND `attrdef` = " . $this->_attrdef->getId();
220                }
221                break;
222            default:
223                return false;
224        }
225        if (!$db->getResult($queryStr))
226            return false;
227
228        $oldvalue = $this->_value;
229        $this->_value = $values;
230
231        /* Check if 'onPostUpdateAttribute' callback is set */
232        $kk = (trim($value) === '') ? 'Remove' : 'Update';
233        if (isset($this->_dms->callbacks['onPost'.$kk.'Attribute'])) {
234            foreach ($this->_dms->callbacks['onPost'.$kk.'Attribute'] as $callback) {
235                if (!call_user_func($callback[0], $callback[1], $this->_obj, $this->_attrdef, $value, $oldvalue)) {
236                }
237            }
238        }
239
240        return true;
241    } /* }}} */
242
243    /**
244     * Validate attribute value
245     *
246     * This function checks if the attribute values fits the attribute
247     * definition.
248     * If the validation fails the validation error will be set which
249     * can be requested by SeedDMS_Core_Attribute::getValidationError()
250     *
251     * @return boolean true if validation succeeds, otherwise false
252     */
253    public function validate() { /* {{{ */
254        /** @var SeedDMS_Core_AttributeDefinition $attrdef */
255        $attrdef = $this->_attrdef;
256        $result = $attrdef->validate($this->_value);
257        $this->_validation_error = $attrdef->getValidationError();
258        return $result;
259    } /* }}} */
260
261    /**
262     * Get validation error from last validation
263     *
264     * @return integer error code
265     */
266    public function getValidationError() { return $this->_validation_error; }
267
268    /**
269     * Set validation error
270     *
271     * @param integer error code
272     */
273    public function setValidationError($error) { $this->_validation_error = $error; }
274
275    /**
276     * Get definition of attribute
277     *
278     * @return object attribute definition
279     */
280    public function getAttributeDefinition() { return $this->_attrdef; }
281
282} /* }}} */
283
284/**
285 * Class to represent an attribute definition in the document management system
286 *
287 * Attribute definitions specify the name, type, object type, minimum and
288 * maximum values and a value set. The object type determines the object
289 * an attribute may be attached to. If the object type is set to object_all
290 * the attribute can be used for documents, document content and folders.
291 *
292 * The type of an attribute specifies the skalar data type.
293 *
294 * Attributes for which multiple values are allowed must have the
295 * multiple flag set to true and specify a value set. A value set
296 * is a string consisting of n separated values. The separator is the
297 * first char of the value set. A possible value could be '|REV-A|REV-B'
298 * If multiple values are allowed, then minvalues and maxvalues may
299 * restrict the allowed number of values.
300 *
301 * @see SeedDMS_Core_Attribute
302 *
303 * @category   DMS
304 * @package    SeedDMS_Core
305 * @author     Markus Westphal, Malcolm Cowe, Uwe Steinmann <uwe@steinmann.cx>
306 * @copyright  Copyright (C) 2012-2024 Uwe Steinmann
307 * @version    Release: @package_version@
308 */
309class SeedDMS_Core_AttributeDefinition { /* {{{ */
310    /**
311     * @var integer id of attribute definition
312     *
313     * @access protected
314     */
315    protected $_id;
316
317    /**
318     * @var string name of attribute definition
319     *
320     * @access protected
321     */
322    protected $_name;
323
324    /**
325     * @var string object type of attribute definition. This can be one of
326     * type_int, type_float, type_string, type_boolean, type_url, or type_email.
327     *
328     * @access protected
329     */
330    protected $_type;
331
332    /**
333     * @var string type of attribute definition. This can be one of objtype_all,
334     * objtype_folder, objtype_document, or objtype_documentcontent.
335     *
336     * @access protected
337     */
338    protected $_objtype;
339
340    /**
341     * @var boolean whether an attribute can have multiple values
342     *
343     * @access protected
344     */
345    protected $_multiple;
346
347    /**
348     * @var integer minimum values of an attribute
349     *
350     * @access protected
351     */
352    protected $_minvalues;
353
354    /**
355     * @var integer maximum values of an attribute
356     *
357     * @access protected
358     */
359    protected $_maxvalues;
360
361    /**
362     * @var string list of possible values of an attribute
363     *
364     * @access protected
365     */
366    protected $_valueset;
367
368    /**
369     * @var string regular expression the value must match
370     *
371     * @access protected
372     */
373    protected $_regex;
374
375    /**
376     * @var integer validation error
377     *
378     * @access protected
379     */
380    protected $_validation_error;
381
382    /**
383     * @var SeedDMS_Core_DMS reference to the dms instance this attribute definition belongs to
384     *
385     * @access protected
386     */
387    protected $_dms;
388
389    /**
390     * @var string just the separator of a value set (not used)
391     *
392     * @access protected
393     */
394    protected $_separator;
395
396    /*
397     * Possible skalar data types of an attribute
398     */
399    const type_int = 1;
400    const type_float = 2;
401    const type_string = 3;
402    const type_boolean = 4;
403    const type_url = 5;
404    const type_email = 6;
405    const type_date = 7;
406
407    /*
408     * Addtional data types of an attribute representing objects in seeddms
409     */
410    const type_folder = 101;
411    const type_document = 102;
412    //const type_documentcontent = 103;
413    const type_user = 104;
414    const type_group = 105;
415
416    /*
417     * The object type for which a attribute may be used
418     */
419    const objtype_all = 0;
420    const objtype_folder = 1;
421    const objtype_document = 2;
422    const objtype_documentcontent = 3;
423
424    /*
425     * The validation error codes
426     */
427    const val_error_none = 0;
428    const val_error_min_values = 1;
429    const val_error_max_values = 2;
430    const val_error_boolean = 8;
431    const val_error_int = 6;
432    const val_error_date = 9;
433    const val_error_float = 7;
434    const val_error_regex = 3;
435    const val_error_email = 5;
436    const val_error_url = 4;
437    const val_error_document = 10;
438    const val_error_folder = 11;
439    const val_error_user = 12;
440    const val_error_group = 13;
441    const val_error_valueset = 14;
442
443    /**
444     * Constructor
445     *
446     * @param integer $id internal id of attribute definition
447     * @param string $name name of attribute
448     * @param integer $objtype type of object for which this attribute definition
449     *        may be used.
450     * @param integer $type skalar type of attribute
451     * @param boolean $multiple set to true if multiple values are allowed
452     * @param integer $minvalues minimum number of values
453     * @param integer $maxvalues maximum number of values
454     * @param string $valueset separated list of allowed values, the first char
455     *        is taken as the separator
456     * @param $regex
457     */
458    public function __construct($id, $name, int $objtype, int $type, $multiple, $minvalues, $maxvalues, $valueset, $regex) { /* {{{ */
459        $this->_id = $id;
460        $this->_name = $name;
461        $this->_type = $type;
462        $this->_objtype = $objtype;
463        $this->_multiple = $multiple;
464        $this->_minvalues = $minvalues;
465        $this->_maxvalues = $maxvalues;
466        $this->_valueset = $valueset;
467        $this->_separator = substr($valueset, 0, 1);
468        $this->_regex = $regex;
469        $this->_dms = null;
470        $this->_validation_error = SeedDMS_Core_AttributeDefinition::val_error_none;
471    } /* }}} */
472
473    /**
474     * Set reference to dms
475     *
476     * @param SeedDMS_Core_DMS $dms
477     */
478    public function setDMS($dms) { /* {{{ */
479        $this->_dms = $dms;
480    } /* }}} */
481
482    /**
483     * Get dms of attribute definition
484     *
485     * @return object $dms
486     */
487    public function getDMS() { return $this->_dms; }
488
489    /**
490     * Get internal id of attribute definition
491     *
492     * @return integer id
493     */
494    public function getID() { return $this->_id; }
495
496    /**
497     * Get name of attribute definition
498     *
499     * @return string name
500     */
501    public function getName() { return $this->_name; }
502
503    /**
504     * Set name of attribute definition
505     *
506     * @param string $name name of attribute definition
507     * @return boolean true on success, otherwise false
508     */
509    public function setName($name) { /* {{{ */
510        $db = $this->_dms->getDB();
511
512        $queryStr = "UPDATE `tblAttributeDefinitions` SET `name` =".$db->qstr($name)." WHERE `id` = " . $this->_id;
513        $res = $db->getResult($queryStr);
514        if (!$res)
515            return false;
516
517        $this->_name = $name;
518        return true;
519    } /* }}} */
520
521    /**
522     * Get object type of attribute definition
523     *
524     * This can be one of objtype_all,
525     * objtype_folder, objtype_document, or objtype_documentcontent.
526     *
527     * @return integer type
528     */
529    public function getObjType() { return $this->_objtype; }
530
531    /**
532     * Set object type of attribute definition
533     *
534     * This can be one of objtype_all,
535     * objtype_folder, objtype_document, or objtype_documentcontent.
536     *
537     * @param integer $objtype type
538     * @return bool
539     */
540    public function setObjType($objtype) { /* {{{ */
541        $db = $this->_dms->getDB();
542
543        $queryStr = "UPDATE `tblAttributeDefinitions` SET `objtype` =".intval($objtype)." WHERE `id` = " . $this->_id;
544        $res = $db->getResult($queryStr);
545        if (!$res)
546            return false;
547
548        $this->_objtype = $objtype;
549        return true;
550    } /* }}} */
551
552    /**
553     * Get type of attribute definition
554     *
555     * This can be one of type_int, type_float, type_string, type_boolean,
556     * type_url, type_email.
557     *
558     * @return integer type
559     */
560    public function getType() { return $this->_type; }
561
562    /**
563     * Set type of attribute definition
564     *
565     * This can be one of type_int, type_float, type_string, type_boolean,
566     * type_url, type_email.
567     *
568     * @param integer $type type
569     * @return bool
570     */
571    public function setType($type) { /* {{{ */
572        $db = $this->_dms->getDB();
573
574        $queryStr = "UPDATE `tblAttributeDefinitions` SET `type` =".intval($type)." WHERE `id` = " . $this->_id;
575        $res = $db->getResult($queryStr);
576        if (!$res)
577            return false;
578
579        $this->_type = $type;
580        return true;
581    } /* }}} */
582
583    /**
584     * Check if attribute definition allows multi values for attribute
585     *
586     * @return boolean true if attribute may have multiple values
587     */
588    public function getMultipleValues() { return $this->_multiple; }
589
590    /**
591     * Set if attribute definition allows multi values for attribute
592     *
593     * @param boolean $mv true if attribute may have multiple values, otherwise
594     * false
595     * @return bool
596     */
597    public function setMultipleValues($mv) { /* {{{ */
598        $db = $this->_dms->getDB();
599
600        $queryStr = "UPDATE `tblAttributeDefinitions` SET `multiple` =".intval($mv)." WHERE `id` = " . $this->_id;
601        $res = $db->getResult($queryStr);
602        if (!$res)
603            return false;
604
605        $this->_multiple = $mv;
606        return true;
607    } /* }}} */
608
609    /**
610     * Return minimum number of values for attributes
611     *
612     * Attributes with multiple values may be limited to a range
613     * of values. This functions returns the minimum number of values.
614     *
615     * @return integer minimum number of values
616     */
617    public function getMinValues() { return $this->_minvalues; }
618
619    public function setMinValues($minvalues) { /* {{{ */
620        $db = $this->_dms->getDB();
621
622        $queryStr = "UPDATE `tblAttributeDefinitions` SET `minvalues` =".intval($minvalues)." WHERE `id` = " . $this->_id;
623        $res = $db->getResult($queryStr);
624        if (!$res)
625            return false;
626
627        $this->_minvalues = $minvalues;
628        return true;
629    } /* }}} */
630
631    /**
632     * Return maximum number of values for attributes
633     *
634     * Attributes with multiple values may be limited to a range
635     * of values. This functions returns the maximum number of values.
636     *
637     * @return integer maximum number of values
638     */
639    public function getMaxValues() { return $this->_maxvalues; }
640
641    public function setMaxValues($maxvalues) { /* {{{ */
642        $db = $this->_dms->getDB();
643
644        $queryStr = "UPDATE `tblAttributeDefinitions` SET `maxvalues` =".intval($maxvalues)." WHERE `id` = " . $this->_id;
645        $res = $db->getResult($queryStr);
646        if (!$res)
647            return false;
648
649        $this->_maxvalues = $maxvalues;
650        return true;
651    } /* }}} */
652
653    /**
654     * Get the value set as saved in the database
655     *
656     * This is a string containing the list of valueÑ• separated by a
657     * delimiter which also precedes the whole string, e.g. '|Yes|No'
658     *
659     * Use {@link SeedDMS_Core_AttributeDefinition::getValueSetAsArray()}
660     * for a list of values returned as an array.
661     *
662     * @return string value set
663     */
664    public function getValueSet() { /* {{{ */
665        return $this->_valueset;
666    } /* }}} */
667
668    /**
669     * Get the separator used for the value set
670     *
671     * This is the first char of the value set string.
672     *
673     * @return string separator or an empty string if a value set is not set
674     */
675    public function getValueSetSeparator() { /* {{{ */
676        if (strlen($this->_valueset) > 1) {
677            return $this->_valueset[0];
678        } elseif ($this->_multiple) {
679            if ($this->_type == SeedDMS_Core_AttributeDefinition::type_boolean) {
680                return '';
681            } else {
682                return ',';
683            }
684        } else {
685            return '';
686        }
687    } /* }}} */
688
689    /**
690     * Get the whole value set as an array
691     *
692     * Each element is trimmed.
693     *
694     * @return array values of value set or false if the value set has
695     *         less than 2 chars
696     */
697    public function getValueSetAsArray() { /* {{{ */
698        if (strlen($this->_valueset) > 1) {
699            return array_map('trim', explode($this->_valueset[0], substr($this->_valueset, 1)));
700        } else {
701            return array();
702        }
703    } /* }}} */
704
705    /**
706     * Get the n'th trimmed value of a value set
707     *
708     * @param $ind starting from 0 for the first element in the value set
709     * @return string n'th value of value set or false if the index is
710     *         out of range or the value set has less than 2 chars
711     * @internal param int $index
712     */
713    public function getValueSetValue($ind) { /* {{{ */
714        if (strlen($this->_valueset) > 1) {
715            $tmp = explode($this->_valueset[0], substr($this->_valueset, 1));
716            if (isset($tmp[$ind])) {
717                return trim($tmp[$ind]);
718            } else {
719                return false;
720            }
721        } else {
722            return false;
723        }
724    } /* }}} */
725
726    /**
727     * Set the value set
728     *
729     * A value set is a list of values allowed for an attribute. The values
730     * are separated by a char which must also be the first char of the
731     * value set string. The method decomposes the value set, removes all
732     * leading and trailing white space from the elements and recombines them
733     * into a string.
734     *
735     * @param string $valueset
736     * @return boolean true if value set could be set, otherwise false
737     */
738    public function setValueSet($valueset) { /* {{{ */
739    /*
740        $tmp = array();
741        foreach ($valueset as $value) {
742            $tmp[] = str_replace('"', '""', $value);
743        }
744        $valuesetstr = implode(",", $tmp);
745     */
746        $valueset = trim($valueset);
747        if ($valueset) {
748            $valuesetarr = array_map('trim', explode($valueset[0], substr($valueset, 1)));
749            $valuesetstr = $valueset[0].implode($valueset[0], $valuesetarr);
750        } else {
751            $valuesetstr = '';
752        }
753
754        $db = $this->_dms->getDB();
755
756        $queryStr = "UPDATE `tblAttributeDefinitions` SET `valueset` =".$db->qstr($valuesetstr)." WHERE `id` = " . $this->_id;
757        $res = $db->getResult($queryStr);
758        if (!$res)
759            return false;
760
761        $this->_valueset = $valuesetstr;
762        $this->_separator = substr($valuesetstr, 0, 1);
763        return true;
764    } /* }}} */
765
766    /**
767     * Get the regular expression as saved in the database
768     *
769     * @return string regular expression
770     */
771    public function getRegex() { /* {{{ */
772        return $this->_regex;
773    } /* }}} */
774
775    /**
776     * Set the regular expression
777     *
778     * A value of the attribute must match this regular expression.
779     *
780     * The methods checks if the regular expression is valid by running
781     * preg_match() on an empty string and see if it fails. Trying to set
782     * an invalid regular expression will not overwrite the current
783     * regular expression.
784     *
785     * All leading and trailing spaces of $regex will be removed.
786     *
787     * @param string $regex
788     * @return boolean true if regex could be set or is invalid, otherwise false
789     */
790    public function setRegex($regex) { /* {{{ */
791        $db = $this->_dms->getDB();
792
793        $regex = trim($regex);
794        if ($regex && @preg_match($regex, '') === false)
795            return false;
796
797        $queryStr = "UPDATE `tblAttributeDefinitions` SET `regex` =".$db->qstr($regex)." WHERE `id` = " . $this->_id;
798        $res = $db->getResult($queryStr);
799        if (!$res)
800            return false;
801
802        $this->_regex = $regex;
803        return true;
804    } /* }}} */
805
806    /**
807     * Check if the attribute definition is used
808     *
809     * Checks all documents, folders and document content whether at least
810     * one of them referenceÑ• this attribute definition
811     *
812     * @return boolean true if attribute definition is used, otherwise false
813     */
814    public function isUsed() { /* {{{ */
815        $db = $this->_dms->getDB();
816
817        $queryStr = "SELECT * FROM `tblDocumentAttributes` WHERE `attrdef`=".$this->_id;
818        $resArr = $db->getResultArray($queryStr);
819        if (is_array($resArr) && count($resArr) == 0) {
820            $queryStr = "SELECT * FROM `tblFolderAttributes` WHERE `attrdef`=".$this->_id;
821            $resArr = $db->getResultArray($queryStr);
822            if (is_array($resArr) && count($resArr) == 0) {
823                $queryStr = "SELECT * FROM `tblDocumentContentAttributes` WHERE `attrdef`=".$this->_id;
824                $resArr = $db->getResultArray($queryStr);
825                if (is_array($resArr) && count($resArr) == 0) {
826                    return false;
827                }
828            }
829        }
830        return true;
831    } /* }}} */
832
833    /**
834     * Parse a given value stored in the database according to attribute definition
835     *
836     * The return value is an array, if the attribute allows multiple values.
837     * Otherwise it is a single value.
838     * If the type of attribute is any of document, folder, user,
839     * or group then this method will fetch each object from the database and
840     * return an array of SeedDMS_Core_Document, SeedDMS_Core_Folder, etc.
841     *
842     * @param $value string
843     * @return array|bool
844     */
845    public function parseValue(string $value) { /* {{{ */
846        if ($this->getMultipleValues()) {
847            /* If the value doesn't start with the separator used in the value set,
848             * then assume that the value was not saved with a leading separator.
849             * This can happen, if the value was previously a single value from
850             * the value set and later turned into a multi value attribute.
851             */
852            $sep = substr($value, 0, 1);
853            $vsep = $this->getValueSetSeparator();
854            if ($sep == $vsep) {
855                $values = explode($sep, substr($value, 1));
856            } else {
857                $values = array($value);
858            }
859        } else {
860            $values = array($value);
861        }
862
863        $tmp = [];
864        switch ((string) $this->getType()) {
865        case self::type_document:
866            foreach ($values as $value) {
867                if ($u = $this->_dms->getDocument((int) $value))
868                    $tmp[] = $u;
869            }
870            $values = $tmp;
871            break;
872        case self::type_folder:
873            foreach ($values as $value) {
874                if ($u = $this->_dms->getFolder((int) $value))
875                    $tmp[] = $u;
876            }
877            $values = $tmp;
878            break;
879        case self::type_user:
880            foreach ($values as $value) {
881                if ($u = $this->_dms->getUser((int) $value))
882                    $tmp[] = $u;
883            }
884            $values = $tmp;
885            break;
886        case self::type_group:
887            foreach ($values as $value) {
888                if ($u = $this->_dms->getGroup((int) $value))
889                    $tmp[] = $u;
890            }
891            $values = $tmp;
892            break;
893        case self::type_boolean:
894            foreach ($values as $value) {
895                $tmp[] = (bool) $value;
896            }
897            $values = $tmp;
898            break;
899        case self::type_int:
900            foreach ($values as $value) {
901                $tmp[] = (int) $value;
902            }
903            $values = $tmp;
904            break;
905        case self::type_float:
906            foreach ($values as $value) {
907                $tmp[] = (float) $value;
908            }
909            $values = $tmp;
910            break;
911        }
912
913        if ($this->getMultipleValues()) {
914            return $values;
915        } else {
916            return $values ? $values[0] : null;
917        }
918    } /* }}} */
919
920    /**
921     * Create the value stored in the database
922     */
923    public function createValue($values) { /* {{{ */
924        if (is_array($values)) {
925            switch ($this->getType()) {
926            case SeedDMS_Core_AttributeDefinition::type_document:
927            case SeedDMS_Core_AttributeDefinition::type_folder:
928            case SeedDMS_Core_AttributeDefinition::type_user:
929            case SeedDMS_Core_AttributeDefinition::type_group:
930                $tmp = array_map(fn($value): int => is_object($value) ? (int) $value->getId() : (int) $value, $values);
931                break;
932            case SeedDMS_Core_AttributeDefinition::type_boolean:
933                $tmp = array_map(fn($value): int => $value ? '1' : '0', $values);
934                break;
935            default:
936                $tmp = $values;
937            }
938        } else {
939            switch ($this->getType()) {
940            case SeedDMS_Core_AttributeDefinition::type_document:
941            case SeedDMS_Core_AttributeDefinition::type_folder:
942            case SeedDMS_Core_AttributeDefinition::type_user:
943            case SeedDMS_Core_AttributeDefinition::type_group:
944                $tmp = is_object($values) ? [$values->getId()] : (is_numeric($values) ? [$values] : []);
945                break;
946            case SeedDMS_Core_AttributeDefinition::type_boolean:
947                $tmp = [$values ? 1 : 0];
948                break;
949            default:
950                $tmp = [$values];
951            }
952        }
953
954        if ($this->getMultipleValues()) {
955            $vsep = $this->getValueSetSeparator();
956        } else {
957            $vsep = '';
958        }
959        return $vsep.implode($vsep, $tmp);
960    } /* }}} */
961
962    /**
963     * Return a list of documents, folders, document contents where this
964     * attribute definition is used
965     *
966     * @param integer $limit return not more the n objects of each type
967     * @return array|bool
968     */
969    public function getStatistics($limit = 0) { /* {{{ */
970        $db = $this->_dms->getDB();
971
972        $result = array('docs'=>array(), 'folders'=>array(), 'contents'=>array());
973        if ($this->_objtype == SeedDMS_Core_AttributeDefinition::objtype_all ||
974           $this->_objtype == SeedDMS_Core_AttributeDefinition::objtype_document) {
975            $queryStr = "SELECT * FROM `tblDocumentAttributes` WHERE `attrdef`=".$this->_id;
976            if ($limit)
977                $queryStr .= " limit ".(int) $limit;
978            $resArr = $db->getResultArray($queryStr);
979            if ($resArr) {
980                foreach ($resArr as $rec) {
981                    if ($doc = $this->_dms->getDocument($rec['document'])) {
982                        $result['docs'][] = $doc;
983                    }
984                }
985            }
986            $valueset = $this->getValueSetAsArray();
987            $possiblevalues = array();
988            foreach ($valueset as $value) {
989                $possiblevalues[md5($value)] = array('value'=>$value, 'c'=>0);
990            }
991            $queryStr = "SELECT count(*) c, `value` FROM `tblDocumentAttributes` WHERE `attrdef`=".$this->_id." GROUP BY `value` ORDER BY c DESC";
992            $resArr = $db->getResultArray($queryStr);
993            if ($resArr) {
994                foreach ($resArr as $row) {
995                    $value = $this->parseValue($row['value']);
996                    $tmpattr = new SeedDMS_Core_Attribute(0, null, $this, $value);
997                    foreach ($tmpattr->getValueAsArray() as $value) {
998                        if (is_object($value)) {
999                            $key = md5((string) $value->getId());
1000                        } else {
1001                            $key = md5((string) $value);
1002                        }
1003                        if (isset($possiblevalues[$key])) {
1004                            $possiblevalues[$key]['c'] += $row['c'];
1005                        } else {
1006                            $possiblevalues[$key] = array('value'=>$value, 'c'=>$row['c']);
1007                        }
1008                    }
1009                }
1010                $result['frequencies']['document'] = $possiblevalues;
1011            }
1012        }
1013
1014        if ($this->_objtype == SeedDMS_Core_AttributeDefinition::objtype_all ||
1015           $this->_objtype == SeedDMS_Core_AttributeDefinition::objtype_folder) {
1016            $queryStr = "SELECT * FROM `tblFolderAttributes` WHERE `attrdef`=".$this->_id;
1017            if ($limit)
1018                $queryStr .= " limit ".(int) $limit;
1019            $resArr = $db->getResultArray($queryStr);
1020            if ($resArr) {
1021                foreach ($resArr as $rec) {
1022                    if ($folder = $this->_dms->getFolder($rec['folder'])) {
1023                        $result['folders'][] = $folder;
1024                    }
1025                }
1026            }
1027            $valueset = $this->getValueSetAsArray();
1028            $possiblevalues = array();
1029            foreach ($valueset as $value) {
1030                $possiblevalues[md5($value)] = array('value'=>$value, 'c'=>0);
1031            }
1032            $queryStr = "SELECT count(*) c, `value` FROM `tblFolderAttributes` WHERE `attrdef`=".$this->_id." GROUP BY `value` ORDER BY c DESC";
1033            $resArr = $db->getResultArray($queryStr);
1034            if ($resArr) {
1035                foreach ($resArr as $row) {
1036                    $value = $this->parseValue($row['value']);
1037                    $tmpattr = new SeedDMS_Core_Attribute(0, null, $this, $value);
1038                    foreach ($tmpattr->getValueAsArray() as $value) {
1039                        if (is_object($value)) {
1040                            $key = md5((string) $value->getId());
1041                        } else {
1042                            $key = md5((string) $value);
1043                        }
1044                        if (isset($possiblevalues[$key])) {
1045                            $possiblevalues[$key]['c'] += $row['c'];
1046                        } else {
1047                            $possiblevalues[$key] = array('value'=>$value, 'c'=>$row['c']);
1048                        }
1049                    }
1050                }
1051                $result['frequencies']['folder'] = $possiblevalues;
1052            }
1053        }
1054
1055        if ($this->_objtype == SeedDMS_Core_AttributeDefinition::objtype_all ||
1056           $this->_objtype == SeedDMS_Core_AttributeDefinition::objtype_documentcontent) {
1057            $queryStr = "SELECT * FROM `tblDocumentContentAttributes` WHERE `attrdef`=".$this->_id;
1058            if ($limit)
1059                $queryStr .= " limit ".(int) $limit;
1060            $resArr = $db->getResultArray($queryStr);
1061            if ($resArr) {
1062                foreach ($resArr as $rec) {
1063                    if ($content = $this->_dms->getDocumentContent($rec['content'])) {
1064                        $result['contents'][] = $content;
1065                    }
1066                }
1067            }
1068            $valueset = $this->getValueSetAsArray();
1069            $possiblevalues = array();
1070            foreach ($valueset as $value) {
1071                $possiblevalues[md5($value)] = array('value'=>$value, 'c'=>0);
1072            }
1073            $queryStr = "SELECT count(*) c, `value` FROM `tblDocumentContentAttributes` WHERE `attrdef`=".$this->_id." GROUP BY `value` ORDER BY c DESC";
1074            $resArr = $db->getResultArray($queryStr);
1075            if ($resArr) {
1076                foreach ($resArr as $row) {
1077                    $value = $this->parseValue($row['value']);
1078                    $tmpattr = new SeedDMS_Core_Attribute(0, null, $this, $value);
1079                    foreach ($tmpattr->getValueAsArray() as $value) {
1080                        if (is_object($value)) {
1081                            $key = md5((string) $value->getId());
1082                        } else {
1083                            $key = md5((string) $value);
1084                        }
1085                        if (isset($possiblevalues[$key])) {
1086                            $possiblevalues[$key]['c'] += $row['c'];
1087                        } else {
1088                            $possiblevalues[$key] = array('value'=>$value, 'c'=>$row['c']);
1089                        }
1090                    }
1091                }
1092                $result['frequencies']['content'] = $possiblevalues;
1093            }
1094        }
1095
1096        return $result;
1097    } /* }}} */
1098
1099    /**
1100     * Remove the attribute definition
1101     * Removal is only executed when the definition is not used anymore.
1102     *
1103     * @return boolean true on success or false in case of an error
1104     */
1105    public function remove() { /* {{{ */
1106        $db = $this->_dms->getDB();
1107
1108        if ($this->isUsed())
1109            return false;
1110
1111        // Delete user itself
1112        $queryStr = "DELETE FROM `tblAttributeDefinitions` WHERE `id` = " . $this->_id;
1113        if (!$db->getResult($queryStr)) return false;
1114
1115        return true;
1116    } /* }}} */
1117
1118    /**
1119     * Get all documents and folders by a given attribute value
1120     *
1121     * @param string $attrvalue value of attribute
1122     * @param integer $limit limit number of documents/folders
1123     * @return array array containing list of documents and folders
1124     */
1125    public function getObjects($attrvalue, $limit = 0, $op = O_EQ) { /* {{{ */
1126        $db = $this->_dms->getDB();
1127
1128        $result = array('docs'=>array(), 'folders'=>array(), 'contents'=>array());
1129        if ($this->_objtype == SeedDMS_Core_AttributeDefinition::objtype_all ||
1130          $this->_objtype == SeedDMS_Core_AttributeDefinition::objtype_document) {
1131            $queryStr = "SELECT * FROM `tblDocumentAttributes` WHERE `attrdef`=".$this->_id;
1132            if ($attrvalue != null) {
1133                $queryStr .= " AND ";
1134                if ($this->getMultipleValues()) {
1135                    $sep = $this->getValueSetSeparator();
1136                    $queryStr .= "(`value` like ".$db->qstr($sep.$attrvalue.'%')." OR `value` like ".$db->qstr('%'.$sep.$attrvalue.$sep.'%')." OR `value` like ".$db->qstr('%'.$sep.$attrvalue).")";
1137                } else {
1138                    $queryStr .= "`value`".$op.$db->qstr((string) $attrvalue);
1139                }
1140            }
1141            if ($limit)
1142                $queryStr .= " limit ".(int) $limit;
1143            $resArr = $db->getResultArray($queryStr);
1144            if ($resArr) {
1145                foreach ($resArr as $rec) {
1146                    if ($doc = $this->_dms->getDocument($rec['document'])) {
1147                        $result['docs'][] = $doc;
1148                    }
1149                }
1150            }
1151        }
1152
1153        if ($this->_objtype == SeedDMS_Core_AttributeDefinition::objtype_all ||
1154           $this->_objtype == SeedDMS_Core_AttributeDefinition::objtype_folder) {
1155            $queryStr = "SELECT * FROM `tblFolderAttributes` WHERE `attrdef`=".$this->_id." AND ";
1156            if ($this->getMultipleValues()) {
1157                $sep = $this->getValueSetSeparator();
1158                $queryStr .= "(`value` like ".$db->qstr($sep.$attrvalue.'%')." OR `value` like ".$db->qstr('%'.$sep.$attrvalue.$sep.'%')." OR `value` like ".$db->qstr('%'.$sep.$attrvalue).")";
1159            } else {
1160                $queryStr .= "`value`=".$db->qstr($attrvalue);
1161            }
1162            if ($limit)
1163                $queryStr .= " limit ".(int) $limit;
1164            $resArr = $db->getResultArray($queryStr);
1165            if ($resArr) {
1166                foreach ($resArr as $rec) {
1167                    if ($folder = $this->_dms->getFolder($rec['folder'])) {
1168                        $result['folders'][] = $folder;
1169                    }
1170                }
1171            }
1172        }
1173
1174        return $result;
1175    } /* }}} */
1176
1177    /**
1178     * Remove a given attribute value from all documents, versions and folders
1179     *
1180     * @param string $attrvalue value of attribute
1181     * @return boolean true on success, otherwise false
1182     */
1183    public function removeValue($attrvalue) { /* {{{ */
1184        $db = $this->_dms->getDB();
1185
1186        foreach (array('document', 'documentcontent', 'folder') as $type) {
1187            if ($type == 'document') {
1188                $tablename = "tblDocumentAttributes";
1189                $objtype = SeedDMS_Core_AttributeDefinition::objtype_document;
1190            } elseif ($type == 'documentcontent') {
1191                $tablename = "tblDocumentContentAttributes";
1192                $objtype = SeedDMS_Core_AttributeDefinition::objtype_documentcontent;
1193            } elseif ($type == 'folder') {
1194                $tablename = "tblFolderAttributes";
1195                $objtype = SeedDMS_Core_AttributeDefinition::objtype_folder;
1196            }
1197            if ($this->_objtype == SeedDMS_Core_AttributeDefinition::objtype_all || $objtype) {
1198                /* If and attribute may have multiple values they are stored as
1199                 * <sep><value><sep><value>.... Deleting one of them may not result in an
1200                 * empty attribute, but reduces the list of values.
1201                 */
1202                if ($this->getMultipleValues()) {
1203                    $queryStr = "SELECT * FROM `".$tablename."` WHERE `attrdef`=".$this->_id." AND ";
1204                    $sep = $this->getValueSetSeparator();
1205                    $queryStr .= "(`value` like ".$db->qstr($sep.$attrvalue.'%')." OR `value` like ".$db->qstr('%'.$sep.$attrvalue.$sep.'%')." OR `value` like ".$db->qstr('%'.$sep.$attrvalue).")";
1206                    $resArr = $db->getResultArray($queryStr);
1207                    if ($resArr) {
1208                        $db->startTransaction();
1209                        foreach ($resArr as $rec) {
1210                            if ($rec['value'] == $attrvalue) {
1211                                /* Can this happen? The $attrvalue does not start with the separator */
1212                                $queryStr = "DELETE FROM `".$tablename."` WHERE `id`=".$rec['id'];
1213                            } else {
1214                                $sep = substr($rec['value'], 0, 1);
1215                                $vsep = $this->getValueSetSeparator();
1216                                if ($sep == $vsep) {
1217                                    $values = explode($sep, substr($rec['value'], 1));
1218                                } else {
1219                                    $values = array($rec['value']);
1220                                }
1221                                if (($key = array_search($attrvalue, $values)) !== false) {
1222                                    unset($values[$key]);
1223                                }
1224                                if ($values) {
1225                                    $queryStr = "UPDATE `".$tablename."` SET `value`=".$db->qstr($sep.implode($sep, $values))." WHERE `id`=".$rec['id'];
1226                                } else {
1227                                    $queryStr = "DELETE FROM `".$tablename."` WHERE `id`=".$rec['id'];
1228                                }
1229                            }
1230                            if (!$db->getResult($queryStr)) {
1231                                $db->rollbackTransaction();
1232                                return false;
1233                            }
1234                        }
1235                        $db->commitTransaction();
1236                    }
1237                } else {
1238                    $queryStr = "DELETE FROM `".$tablename."` WHERE `attrdef`=".$this->_id." AND ";
1239                    $queryStr .= "`value`=".$db->qstr($attrvalue);
1240                    if (!$db->getResult($queryStr)) {
1241                        return false;
1242                    }
1243                }
1244            }
1245        }
1246        return true;
1247    } /* }}} */
1248
1249    /**
1250     * Update a given attribute value from all documents, versions and folders
1251     *
1252     * @param string $oldvalue value of attribute
1253     * @param string $newvalue value of attribute
1254     * @return boolean true on success, otherwise false
1255     */
1256    public function updateValue($oldvalue, $newvalue) { /* {{{ */
1257        $db = $this->_dms->getDB();
1258
1259        foreach (array('document', 'documentcontent', 'folder') as $type) {
1260            if ($type == 'document') {
1261                $tablename = "tblDocumentAttributes";
1262                $objtype = SeedDMS_Core_AttributeDefinition::objtype_document;
1263            } elseif ($type == 'documentcontent') {
1264                $tablename = "tblDocumentContentAttributes";
1265                $objtype = SeedDMS_Core_AttributeDefinition::objtype_documentcontent;
1266            } elseif ($type == 'folder') {
1267                $tablename = "tblFolderAttributes";
1268                $objtype = SeedDMS_Core_AttributeDefinition::objtype_folder;
1269            }
1270            if ($this->_objtype == SeedDMS_Core_AttributeDefinition::objtype_all || $objtype) {
1271                /* If and attribute may have multiple values they are stored as
1272                 * <sep><value><sep><value>.... Deleting one of them may not result in an
1273                 * empty attribute, but reduces the list of values.
1274                 */
1275                if ($this->getMultipleValues()) {
1276                    $queryStr = "SELECT * FROM `".$tablename."` WHERE `attrdef`=".$this->_id." AND ";
1277                    $sep = $this->getValueSetSeparator();
1278                    $queryStr .= "(`value` like ".$db->qstr($sep.$oldvalue.'%')." OR `value` like ".$db->qstr('%'.$sep.$oldvalue.$sep.'%')." OR `value` like ".$db->qstr('%'.$sep.$oldvalue).")";
1279                    $resArr = $db->getResultArray($queryStr);
1280                    if ($resArr) {
1281                        $db->startTransaction();
1282                        foreach ($resArr as $rec) {
1283                            $sep = substr($rec['value'], 0, 1);
1284                            $vsep = $this->getValueSetSeparator();
1285                            if ($sep == $vsep) {
1286                                $values = explode($sep, substr($rec['value'], 1));
1287                            } else {
1288                                $values = array($rec['value']);
1289                            }
1290                            if (($key = array_search($attrvalue, $values)) !== false) {
1291                                if ($newvalue)
1292                                    $values[$key] = $newvalue;
1293                                else
1294                                    unset($values[$key]);
1295                            }
1296                            if ($values) {
1297                                $queryStr = "UPDATE `".$tablename."` SET `value`=".$db->qstr($sep.implode($sep, $values))." WHERE `id`=".$rec['id'];
1298                            } else {
1299                                $queryStr = "DELETE FROM `".$tablename."` WHERE `id`=".$rec['id'];
1300                            }
1301                            if (!$db->getResult($queryStr)) {
1302                                $db->rollbackTransaction();
1303                                return false;
1304                            }
1305                        }
1306                        $db->commitTransaction();
1307                    }
1308                } else {
1309                    if ($newvalue)
1310                        $queryStr = "UPDATE `".$tablename."` SET `value`=".$db->qstr($newvalue)." WHERE `attrdef`=".$this->_id." AND ";
1311                    else
1312                        $queryStr = "DELETE FROM `".$tablename."` WHERE `attrdef`=".$this->_id." AND ";
1313                    $queryStr .= "`value`=".$db->qstr($oldvalue);
1314                    if (!$db->getResult($queryStr)) {
1315                        return false;
1316                    }
1317                }
1318            }
1319        }
1320        return true;
1321    } /* }}} */
1322
1323    /**
1324     * Validate value against attribute definition
1325     *
1326     * This function checks if the given value fits the attribute
1327     * definition.
1328     * If the validation fails the validation error will be set which
1329     * can be requested by SeedDMS_Core_Attribute::getValidationError()
1330     * Set $new to true if the value to be checked isn't saved to the database
1331     * already. It will just be passed to the callback onAttributeValidate where
1332     * it could be used to, e.g. check if a value is unique once it is saved to
1333     * the database. $object is set to a folder, document or documentcontent
1334     * if the attribute belongs to such an object. This will be null, if a
1335     * new object is created.
1336     *
1337     * @param string|array $attrvalue attribute value
1338     * @param object $object set if the current attribute is saved for this object
1339     *   (this will only be passed to the onAttributeValidate callback)
1340     * @param boolean $new set to true if the value is new value and not taken from
1341     *   an existing attribute
1342     *   (this will only be passed to the onAttributeValidate callback)
1343     * @return boolean true if validation succeeds, otherwise false
1344     */
1345    public function validate($attrvalue, $object = null, $new = false) { /* {{{ */
1346        /* Check if 'onAttributeValidate' callback is set */
1347        if (isset($this->_dms->callbacks['onAttributeValidate'])) {
1348            foreach ($this->_dms->callbacks['onAttributeValidate'] as $callback) {
1349                $ret = call_user_func($callback[0], $callback[1], $this, $attrvalue, $object, $new);
1350                if (is_bool($ret))
1351                    return $ret;
1352            }
1353        }
1354
1355        /* Turn $attrvalue into an array of values. Checks if $attrvalue starts
1356         * with a separator char as set in the value set and use it to explode
1357         * the $attrvalue. If the separator doesn't match or this attribute
1358         * definition doesn't have a value set, then just create a one element
1359         * array. if $attrvalue is empty, then create an empty array.
1360         */
1361        if ($this->getMultipleValues()) {
1362            if (is_string($attrvalue) && $attrvalue) {
1363                $sep = $attrvalue[0];
1364                $vsep = $this->getValueSetSeparator();
1365                if ($sep == $vsep) {
1366                    $values = explode($attrvalue[0], substr($attrvalue, 1));
1367                } else {
1368                    $values = array($attrvalue);
1369                }
1370            } elseif (is_array($attrvalue)) {
1371                $values = $attrvalue;
1372            } elseif (is_string($attrvalue) && !$attrvalue) {
1373                $values = array();
1374            } else {
1375                $values = array($attrvalue);
1376            }
1377        } elseif ($attrvalue !== null) {
1378            $values = array($attrvalue);
1379        } else {
1380            $values = array();
1381        }
1382
1383        /* Check if attribute value has at least the minimum number of values */
1384        $this->_validation_error = SeedDMS_Core_AttributeDefinition::val_error_none;
1385        if ($this->getMinValues() > count($values)) {
1386            $this->_validation_error = SeedDMS_Core_AttributeDefinition::val_error_min_values;
1387            return false;
1388        }
1389        /* Check if attribute value has not more than maximum number of values */
1390        if ($this->getMaxValues() && $this->getMaxValues() < count($values)) {
1391            $this->_validation_error = SeedDMS_Core_AttributeDefinition::val_error_max_values;
1392            return false;
1393        }
1394
1395        $success = true;
1396        switch ((string) $this->getType()) {
1397        case self::type_boolean:
1398            foreach ($values as $value) {
1399                $success = $success && (preg_match('/^[01]$/', (string) $value) || $value === true || $value === false);
1400            }
1401            if (!$success)
1402                $this->_validation_error = SeedDMS_Core_AttributeDefinition::val_error_boolean;
1403            break;
1404        case self::type_int:
1405            foreach ($values as $value) {
1406                $success = $success && (preg_match('/^[0-9]*$/', (string) $value) ? true : false);
1407            }
1408            if (!$success)
1409                $this->_validation_error = SeedDMS_Core_AttributeDefinition::val_error_int;
1410            break;
1411        case self::type_date:
1412            foreach ($values as $value) {
1413                $d = explode('-', $value, 3);
1414                $success = $success && (count($d) == 3) && checkdate((int) $d[1], (int) $d[2], (int) $d[0]);
1415            }
1416            if (!$success)
1417                $this->_validation_error = SeedDMS_Core_AttributeDefinition::val_error_date;
1418            break;
1419        case self::type_float:
1420            foreach ($values as $value) {
1421                $success = $success && is_numeric($value);
1422            }
1423            if (!$success)
1424                $this->_validation_error = SeedDMS_Core_AttributeDefinition::val_error_float;
1425            break;
1426        case self::type_string:
1427            if (trim($this->getRegex()) != '') {
1428                foreach ($values as $value) {
1429                    $success = $success && (preg_match($this->getRegex(), $value) ? true : false);
1430                }
1431            }
1432            if (!$success)
1433                $this->_validation_error = SeedDMS_Core_AttributeDefinition::val_error_regex;
1434            break;
1435        case self::type_email:
1436            foreach ($values as $value) {
1437                //$success &= filter_var($value, FILTER_VALIDATE_EMAIL) ? true : false;
1438                $success = $success && (preg_match('/^[a-z0-9._-]+@[a-z0-9-]{2,63}(\.[a-z0-9-]{2,63})*\.[a-z]{2,63}$/i', $value) ? true : false);
1439            }
1440            if (!$success)
1441                $this->_validation_error = SeedDMS_Core_AttributeDefinition::val_error_email;
1442            break;
1443        case self::type_url:
1444            foreach ($values as $value) {
1445                $success = $success && (preg_match('/^http(s)?:\/\/[a-z0-9_-]+(\.[a-z0-9-]{2,63})*(:[0-9]+)?(\/.*)?$/i', $value) ? true : false);
1446            }
1447            if (!$success)
1448                $this->_validation_error = SeedDMS_Core_AttributeDefinition::val_error_url;
1449            break;
1450        case self::type_document:
1451            $success = true;
1452            foreach ($values as $value) {
1453                if (!$value->isType('document'))
1454                    $success = $success && false;
1455            }
1456            if (!$success)
1457                $this->_validation_error = SeedDMS_Core_AttributeDefinition::val_error_document;
1458            break;
1459        case self::type_folder:
1460            $success = true;
1461            foreach ($values as $value) {
1462                if (!$value->isType('folder'))
1463                    $success = $success && false;
1464            }
1465            if (!$success)
1466                $this->_validation_error = SeedDMS_Core_AttributeDefinition::val_error_folder;
1467            break;
1468        case self::type_user:
1469            $success = true;
1470            foreach ($values as $value) {
1471                if (!$value->isType('user'))
1472                    $success = $success && false;
1473            }
1474            if (!$success)
1475                $this->_validation_error = SeedDMS_Core_AttributeDefinition::val_error_user;
1476            break;
1477        case self::type_group:
1478            $success = true;
1479            foreach ($values as $value) {
1480                if (!$value->isType('group'))
1481                    $success = $success && false;
1482            }
1483            if (!$success)
1484                $this->_validation_error = SeedDMS_Core_AttributeDefinition::val_error_group;
1485            break;
1486        }
1487
1488        if (!$success)
1489            return $success;
1490
1491        /* Check if value is in value set */
1492        if ($valueset = $this->getValueSetAsArray()) {
1493            /* An empty value cannot be the value set */
1494            if (!$values) {
1495                $success = false;
1496                $this->_validation_error = SeedDMS_Core_AttributeDefinition::val_error_valueset;
1497            } else {
1498                foreach ($values as $value) {
1499                    if (!in_array($value, $valueset)) {
1500                        $success = false;
1501                        $this->_validation_error = SeedDMS_Core_AttributeDefinition::val_error_valueset;
1502                    }
1503                }
1504            }
1505        }
1506
1507        return $success;
1508    } /* }}} */
1509
1510    /**
1511     * Get validation error from last validation
1512     *
1513     * @return integer error code
1514     */
1515    public function getValidationError() { return $this->_validation_error; }
1516
1517} /* }}} */