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