[ 'inpage' => true, 'ineditor' => true ] ]; // use previously saved configuration, ignoring all keys that are not supposed to be here if (!is_null($config)) { $this->mergeConfig($config, $this->config); } $this->initTransConfig(); $this->config = array_merge($baseconfig, $this->config); $this->label = $label; $this->ismulti = (bool)$ismulti; $this->tid = $tid; } /** * Merge the current config with the base config of the type * * Ignores all keys that are not supposed to be there. Recurses into sub keys * * @param array $current Current configuration * @param array $config Base Type configuration */ protected function mergeConfig($current, &$config) { foreach ($current as $key => $value) { if (isset($config[$key]) || in_array($key, $this->keepconfig)) { if (isset($config[$key]) && is_array($config[$key])) { $this->mergeConfig($value, $config[$key]); } else { $config[$key] = $value; } } } } /** * Returns data as associative array * * @return array */ public function getAsEntry() { return [ 'config' => json_encode($this->config, JSON_THROW_ON_ERROR), 'label' => $this->label, 'ismulti' => $this->ismulti, 'class' => $this->getClass() ]; } /** * The class name of this type (no namespace) * @return string */ public function getClass() { $class = get_class($this); return substr($class, strrpos($class, "\\") + 1); } /** * Return the current configuration for this type * * @return array */ public function getConfig() { return $this->config; } /** * @return boolean */ public function isMulti() { return $this->ismulti; } /** * @return string */ public function getLabel() { return $this->label; } /** * Returns the translated label for this type * * Uses the current language as determined by $conf['lang']. Falls back to english * and then to the type label * * @return string */ public function getTranslatedLabel() { return $this->getTranslatedKey('label', $this->label); } /** * Returns the translated hint for this type * * Uses the current language as determined by $conf['lang']. Falls back to english. * Returns empty string if no hint is configured * * @return string */ public function getTranslatedHint() { return $this->getTranslatedKey('hint', ''); } /** * @return int */ public function getTid() { return $this->tid; } /** * @return Column * @throws StructException */ public function getContext() { if (is_null($this->context)) { throw new StructException( 'Empty column context requested. Type was probably initialized outside of Schema.' ); } return $this->context; } /** * @param Column $context */ public function setContext($context) { $this->context = $context; } /** * @return bool */ public function isVisibleInEditor() { return $this->config['visibility']['ineditor']; } /** * @return bool */ public function isVisibleInPage() { return $this->config['visibility']['inpage']; } /** * Split a single value into multiple values * * This function is called on saving data when only a single value instead of an array * was submitted. * * Types implementing their own @param string $value * @return array * @see multiValueEditor() will probably want to override this * */ public function splitValues($value) { return array_map('trim', explode(',', $value)); } /** * Return the editor to edit multiple values * * Types can override this to provide a better alternative than multiple entry fields * * @param string $name the form base name where this has to be stored * @param string[] $rawvalues the current values * @param string $htmlID a unique id to be referenced by the label * @return string html */ public function multiValueEditor($name, $rawvalues, $htmlID) { $html = ''; foreach ($rawvalues as $value) { $html .= '
'; $html .= $this->valueEditor($name . '[]', $value, ''); $html .= '
'; } // empty field to add $html .= '
'; $html .= '
'; $html .= $this->valueEditor($name . '[]', '', $htmlID); $html .= '
'; $html .= '
'; return $html; } /** * Return the editor to edit a single value * * @param string $name the form name where this has to be stored * @param string $rawvalue the current value * @param string $htmlID a unique id to be referenced by the label * * @return string html */ public function valueEditor($name, $rawvalue, $htmlID) { $class = 'struct_' . strtolower($this->getClass()); // support the autocomplete configurations out of the box if (isset($this->config['autocomplete']['maxresult']) && $this->config['autocomplete']['maxresult']) { $class .= ' struct_autocomplete'; } $params = [ 'name' => $name, 'value' => $rawvalue, 'class' => $class, 'id' => $htmlID ]; $attributes = buildAttributes($params, true); return ""; } /** * Output the stored data * * @param string|int $value the value stored in the database * @param \Doku_Renderer $R the renderer currently used to render the data * @param string $mode The mode the output is rendered in (eg. XHTML) * @return bool true if $mode could be satisfied */ public function renderValue($value, \Doku_Renderer $R, $mode) { $value = $this->displayValue($value); $R->cdata($value); return true; } /** * format and return the data * * @param int[]|string[] $values the values stored in the database * @param \Doku_Renderer $R the renderer currently used to render the data * @param string $mode The mode the output is rendered in (eg. XHTML) * @return bool true if $mode could be satisfied */ public function renderMultiValue($values, \Doku_Renderer $R, $mode) { $len = count($values); for ($i = 0; $i < $len; $i++) { $this->renderValue($values[$i], $R, $mode); if ($i < $len - 1) { $R->cdata(', '); } } return true; } /** * Render a link in a struct cloud. This should be good for most types, but can be overwritten if necessary. * * @param string|int $value the value stored in the database * @param \Doku_Renderer $R the renderer currently used to render the data * @param string $mode The mode the output is rendered in (eg. XHTML) * @param string $page the target to which should be linked * @param string $filter the filter to apply to the aggregations on $page * @param int $weight the scaled weight of the item. implemented as css font-size on the outside container * @param int|null $showCount count for the tag, only passed if summarize was set in config */ public function renderTagCloudLink($value, \Doku_Renderer $R, $mode, $page, $filter, $weight, $showCount = null) { $value = $this->displayValue($value); if ($showCount) { $value .= " ($showCount)"; } $R->internallink("$page?$filter", $value); } /** * This function is used to modify an aggregation query to add a filter * for the given column matching the given value. A type should add at * least a filter here but could do additional things like joining more * tables needed to handle more complex filters * * Important: $value might be an array. If so, the filter should check against * all provided values ORed together * * @param QueryBuilderWhere $add The where clause where statements can be added * @param string $tablealias The table the currently saved value(s) are stored in * @param string $colname The column name on above table to use in the SQL * @param string $comp The SQL comparator (LIKE, NOT LIKE, =, !=, etc) * @param string|string[] $value this is the user supplied value to compare against. might be multiple * @param string $op the logical operator this filter should use (AND|OR) */ public function filter(QueryBuilderWhere $add, $tablealias, $colname, $comp, $value, $op) { /** @var QueryBuilderWhere $add Where additionional queries are added to */ if (is_array($value)) { $add = $add->where($op); // sub where group $op = 'OR'; } foreach ((array)$value as $item) { $pl = $add->getQB()->addValue($item); $add->where($op, "$tablealias.$colname $comp $pl"); } } /** * Add the proper selection for this type to the current Query * * The default implementation here should be good for nearly all types, it simply * passes the given parameters to the query builder. But type may do more fancy * stuff here, eg. join more tables or select multiple values and combine them to * JSON. If you do, be sure implement a fitting rawValue() method. * * The passed $tablealias.$columnname might be a data_* table (referencing a single * row) or a multi_* table (referencing multiple rows). In the latter case the * multi table has already been joined with the proper conditions. * * You may assume a column alias named 'PID' to be available, should you need the * current page context for a join or sub select. * * @param QueryBuilder $QB * @param string $tablealias The table the currently saved value(s) are stored in * @param string $colname The column name on above table * @param string $alias The added selection *has* to use this column alias */ public function select(QueryBuilder $QB, $tablealias, $colname, $alias) { $QB->addSelectColumn($tablealias, $colname, $alias); } /** * Sort results by this type * * The default implementation should be good for nearly all types. However some * types may need to do proper SQLite type casting to have the right order. * * Generally if you implemented @param QueryBuilder $QB * @param string $tablealias The table the currently saved value is stored in * @param string $colname The column name on above table (always single column!) * @param string $order either ASC or DESC * @see select() you probably want to implement this, * too. * */ public function sort(QueryBuilder $QB, $tablealias, $colname, $order) { $QB->addOrderBy("$tablealias.$colname COLLATE NOCASE $order"); } /** * Get the string by which to sort values of this type * * This implementation is designed to work both as registered function in sqlite * and to provide a string to be used in sorting values of this type in PHP. * * @param string|Value $value The string by which the types would usually be sorted * * @return string */ public function getSortString($value) { if (is_string($value)) { return $value; } $display = $value->getDisplayValue(); if (is_array($display)) { return blank($display[0]) ? "" : $display[0]; } return $display; } /** * This allows types to apply a transformation to the value read by select() * * The returned value should always be a single, non-complex string. In general * it is the identifier a type stores in the database. * * This value will be used wherever the raw saved data is needed for comparisons. * The default implementations of renderValue() and valueEditor() will call this * function as well. * * @param string $value The value as returned by select() * @return string The value as saved in the database */ public function rawValue($value) { return $value; } /** * This is called when a single string is needed to represent this Type's current * value as a single (non-HTML) string. Eg. in a dropdown or in autocompletion. * * @param string $value * @return string */ public function displayValue($value) { return $this->rawValue($value); } /** * This is the value to be used as argument to a filter for another column. * * In a sense this is the counterpart to the @param string $value * * @return string * @see filter() function * */ public function compareValue($value) { return $this->rawValue($value); } /** * Validate and optionally clean a single value * * This function needs to throw a validation exception when validation fails. * The exception message will be prefixed by the appropriate field on output * * The function should return the value as it should be saved later on. * * @param string|int $rawvalue * @return int|string the cleaned value * @throws ValidationException */ public function validate($rawvalue) { return trim($rawvalue); } /** * Overwrite to handle Ajax requests * * A call to DOKU_BASE/lib/exe/ajax.php?call=plugin_struct&column=schema.name will * be redirected to this function on a fully initialized type. The result is * JSON encoded and returned to the caller. Access additional parameter via $INPUT * as usual * * @return mixed * @throws StructException when something goes wrong */ public function handleAjax() { throw new StructException('not implemented'); } /** * Convenience method to access plugin language strings * * @param string $string * @return string */ public function getLang($string) { if (is_null($this->hlp)) $this->hlp = plugin_load('helper', 'struct'); return $this->hlp->getLang($string); } /** * With what comparator should dynamic filters filter this type? * * This default does a LIKE operation * * @return string * @see Search::$COMPARATORS */ public function getDefaultComparator() { return '*~'; } }