* @copyright 2017-2018 * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 * @category Stud.IP * @package resources * @since 4.1 * * @property string $id alias column for property_id * @property string $property_id database column * @property string $name database column * @property I18NString|null $description database column * @property string $type database column * @property string $options database column * @property int $system database column * @property int $info_label database column * @property I18NString $display_name database column * @property int $searchable database column * @property int $range_search database column * @property string $write_permission_level database column * @property int|null $property_group_id database column * @property int|null $property_group_pos database column * @property int $mkdate database column * @property int $chdate database column * @property ResourcePropertyGroup|null $group belongs_to ResourcePropertyGroup */ class ResourcePropertyDefinition extends SimpleORMap { /** * This regular expression is used to ensure that position properties * are always in a format as specified by the ISO-6709 * string representation. */ const CRSWGS84_REGEX = '/[+-]\d{1,3}\.\d{1,10}[+-]\d{1,3}\.\d{1,10}[+-]\d{1,5}\.\d{1,10}CRSWGS_84\/$/'; protected static function configure($config = []) { $config['db_table'] = 'resource_property_definitions'; $config['belongs_to']['group'] = [ 'class_name' => ResourcePropertyGroup::class, 'foreign_key' => 'property_group_id' ]; $config['has_many']['properties'] = [ 'class_name' => ResourceProperty::class, 'assoc_foreign_key' => 'property_id', 'on_delete' => 'delete', ]; $config['i18n_fields']['display_name'] = true; $config['i18n_fields']['description'] = true; parent::configure($config); } public static function findByPropertyGroup($group_id) { return self::findBySql( 'property_group_id = :group_id ORDER BY property_group_pos ASC, name ASC', [ 'group_id' => $group_id ] ); } /** * Returns a list of all defined data types. * * @return string[] An array containing the names of all defined * resource property types. */ public static function getDefinedTypes() { return [ 'bool', 'text', 'num', 'select', 'user', 'institute', 'position', 'fileref', 'url' ]; } /** * Returns all available options for this property * as an array. */ public function getOptionsArray() { if ($this->options) { $options = explode(';', $this->options); return array_combine($options, $options); } return []; } public function setOptionsFromArray($array = []) { if (is_array($array)) { $this->options = implode(';', $array); } else { $this->options = ''; } } /** * Generates appropriate HTML input elements for this property. * * @param string $value The value of the HTML input element. * * @param string $special_name A special name for the HTML input(s). * * @param bool $with_label Whether a label shall be placed around the * HTML input element(s) or not. * * @param bool $allow_boolean_false Wheter boolean attributes shall * also include a hidden input field that sets the value to zero * if the checkbox for the boolean attribute is not checked. * Defaults to true. * * @return string A string containing HTML code. */ public function toHtmlInput( $value = '', $special_name = '', $with_label = false, $allow_boolean_false = true, $disabled = false ) { $label_html_classes = ''; $type = $this->type; $input_name = $special_name ? $special_name : 'properties[' . $this->id . ']'; if ($type === 'bool') { $label_html_classes = 'col-3'; //Booleans can have one or two input elements, //whether a false state shall be selectable or not. if ($allow_boolean_false) { $input_html = sprintf( '' . '', htmlReady($input_name), $value ? 'checked' : '', $disabled ? 'disabled' : '' ); } else { $input_html = sprintf( '', htmlReady($input_name), $value ? 'checked' : '', $disabled ? 'disabled' : '' ); } if ($with_label) { return sprintf( '', ( $label_html_classes ? 'class="' . htmlReady($label_html_classes) . '"' : '' ), $input_html, htmlReady($this->__toString()) ); } else { return $input_html; } } elseif ($type === 'select') { $options_html = sprintf( '', _('Bitte wählen'), !$value ? 'selected="selected"' : '' ); foreach ($this->getOptionsArray() as $option) { $options_html .= sprintf( '', htmlReady($option), $value == $option ? 'selected="selected"' : '' ); } if ($with_label) { return sprintf( '', ( $label_html_classes ? 'class="' . htmlReady($label_html_classes) . '"' : '' ), htmlReady($input_name), $options_html, htmlReady($this->__toString()) ); } else { return sprintf( '', htmlReady($input_name), $options_html ); } } elseif ($type === 'position') { $factory = new Flexi\Factory($GLOBALS['STUDIP_BASE_PATH']); $template = $factory->open('templates/resources/position_attribute_form_part.php'); $template->set_attribute( 'input_name', $input_name ); $template->set_attribute( 'latitude', $value[0] ); $template->set_attribute( 'longitude', $value[1] ); $template->set_attribute( 'altitude', $value[2] ); return $template->render(); } elseif ($type === 'user') { $search = new QuickSearch($input_name, new StandardSearch('user_id')); $search->defaultValue($value, ($value ? get_fullname($value, 'full_rev_username') : '')); return sprintf( '', ( $label_html_classes ? 'class="' . htmlReady($label_html_classes) . '"' : '' ), $this->__toString(), $search->render(), Icon::create('refresh')->asImg( [ 'class' => 'delete-assigned-user-icon enter-accessible', 'data-input-name' => $input_name, 'title' => _('Zuorndung entfernen'), 'tabindex' => '0', 'aria-role' => 'button' ] ) ); } else { $input_type = 'text'; $min = ''; if ($type === 'num') { $input_type = 'number'; $min = 'min="0"'; } if ($with_label) { return sprintf( '', ( $label_html_classes ? 'class="' . htmlReady($label_html_classes) . '"' : '' ), $input_type, htmlReady($input_name), $value, htmlReady($this->__toString()), $disabled ? 'disabled' : '', $min ); } else { return sprintf( '', $input_type, htmlReady($input_name), $value, $disabled ? 'disabled' : '', $min ); } } } /** * Verifies that a property value (state) is valid for the given * resource property definition. * * @param string $state A state for this property which shall be checked. * * @throws ResourcePropertyStateException If the state has an invalid value * a ResourcePropertyStateException is thrown. * * @return bool True, if the state value is valid. */ public function validateState($state = '') { $invalid_state = false; //The type 'text' does not need to be validated since it can have //all sorts of data in it. if ($this->type == 'bool') { if (!in_array($state, ['0', '1'])) { //invalid boolean state: neither true nor false $invalid_state = true; } } elseif ($this->type == 'num') { if (!preg_match('/[0-9.]+/', $state)) { //not a number $invalid_state = true; } } elseif ($this->type == 'user') { if (!User::exists($state)) { //User does not exist throw new ResourcePropertyStateException( sprintf( _('Die Eigenschaft %1$s besitzt einen ungültigen Wert! Der/die Nutzer/-in mit der ID %2$s existiert nicht!'), $this->name, $state ) ); } } elseif ($this->type == 'institute') { if (!Institute::exists($state)) { //Institute does not exist throw new ResourcePropertyStateException( sprintf( _('Die Eigenschaft %1$s besitzt einen ungültigen Wert! Die Einrichtung mit der ID %2$s existiert nicht!'), $this->name, $state ) ); } } elseif ($this->type == 'position') { if (!preg_match(self::CRSWGS84_REGEX, $state)) { //$state does not contain ISO-6709 coordinates //in the CRSWGS84 format! throw new ResourcePropertyStateException( sprintf( _('Die Positionsangabe für die Eigenschaft %1$s ist ungültig!'), $state ) ); } } //A general exception message: if ($invalid_state) { throw new ResourcePropertyException( sprintf( _('Der Wert %1$s ist für die Eigenschaft %2$s (Typ %3$s) nicht zulässig!'), $state, $this->name, $this->type ) ); } return true; } /** * Creates a ResourceProperty object that is automatically linked * to the property definition. The ResourceProperty object is only * created but not stored in the database. * * @param Resource $resource The resource object which shall be extended * by a property. * @param string $state The value of the property that shall be created. * * @throws ResourcePropertyException If $state is invalid for this property. * * @return ResourceProperty A ResourceProperty object * which can be modified. */ public function createResourceProperty(Resource $resource, $state = '') { if ($this->validateState($state)) { $property = new ResourceProperty(); $property->property_id = $this->id; $property->resource_id = $resource->id; $property->state = $state; return $property; } throw new Exception('Could not validate state'); } public function __toString() { return trim($this->display_name) ?: $this->name; } }