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