Source of file ElementSettingExtension.php
Size: 15,231 Bytes - Last Modified: 2020-11-13T10:29:06+00:00
/var/www/docs.ssmods.com/process/src/src/Models/Settings/ElementSettingExtension.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551 | <?php namespace Nobrainer\Elemental\Settings; use NobrainerWeb\Elemental\TemplateData; use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config_ForClass; use SilverStripe\Core\Config\Configurable; use SilverStripe\Forms\FieldGroup; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FormField; use SilverStripe\Forms\HeaderField; use SilverStripe\Forms\HiddenField; use SilverStripe\Forms\Tab; use SilverStripe\Forms\TabSet; use SilverStripe\Forms\TextField; use SilverStripe\ORM\DataExtension; use SilverStripe\View\ArrayData; use SilverStripe\View\ViewableData; class ElementSettingExtension extends DataExtension { use Configurable; /** * The title of this setting extension. * Used to display to the user in the CMS. * If no title is displayed, fallback to the $column name. * * @var string */ private static $title = ''; /** * The column to write to the database. * This value is not displayed in the CMS, but MUST match the main * DB field name, which is used to store the encoded value in. * * @var string */ private static $column = 'BaseSetting'; /** * The tab-root to add the settings to. * * @var string */ private static $rootTab = 'Root.Settings'; /** * The base of the css class name to be used in generation of the unique $Me string. * * @var string */ private static $cssBase = null; /** * The tab to add this setting to. * * @var string */ private static $settingTab = 'Main'; /** * In case no available source values are set, output a default value. * * @var string */ private static $fallbackSource = ['default']; /** * The default value to set in the field(s). * * @var string */ private static $defaultValue = ''; /** * An array of adjustable settings for this object. * These will be encoded as JSON and saved in a single * column in the database. * Think of this field as a virtual $db value for the setting. * * @var array */ private static $settings = []; /** * The DB array works like usually in SilverStripe. * The only requirement is that the main field for holding encoded data, * is named exactly the same as the "private static $column" field value, * otherwise automatic encoding will not work. * The type of this main field should be "Text". * * @var array */ private static $db = []; /** * Because of a glitch in SilverStripe Versioned, it is necessary to escape * the onBeforeWrite() hook after saving once, since SilverStripe will overwrite values * on subsequent writes in the same cycle, resulting in unset values every time. * Explanation by UncleCheese (2009): * https://forumarchive.silverstripe.org/community/forums/data-model-questions/show/6805 * * @var bool */ private $hasEncoded = false; /** * Dynamically create CMS fields according to the names * declared in the "$settings" array. * * Includes internal error checking and throwing. * * @param FieldList $fields * @throws \Exception */ public function updateCMSFields(FieldList $fields) { $this->ensureMainTab($fields); if (!$this->owner->Style) { return; } $tab = $this->getTab(); $settings = $this->getSettings(); if (!($settings && count($settings))) { // If no fields are present, add a hidden field to discourage // SilverStripe from scaffolding the given field. $fields->addFieldToTab($tab, HiddenField::create($this->getColumn())); return; } $fields->addFieldToTab($tab, HeaderField::create($this->getColumn(), $this->getTitle())); $group = []; foreach ($settings as $setting => $_) { if (is_numeric($setting)) { throw new \Exception('Cannot declare non-associative settings array!'); } $type = $this->getFieldFor($setting); if (!in_array(FormField::class, ClassInfo::ancestry($type))) { throw new \Exception('Field type "' . $type . '" is not a valid FormField.'); } $name = $this->getNameFor($setting); $label = $this->getLabelFor($setting); $source = $this->getSourceFor($setting); $value = $this->getValueFor($setting); $group[] = $type::create($name, $label, $source) ->setValue($value); } $fields->addFieldToTab($tab, FieldGroup::create($group)); } /** * Ensure the "Main" tab is always the first settings tab. * This is done by hardcoding the "Main" outside config scope, * and the ensuring that tab exists before adding anything else. * * @param $fields */ private function ensureMainTab($fields) { $root = self::config()->get('rootTab'); $main = 'Main'; $tab = "$root.$main"; if (!$fields->findOrMakeTab($tab)) { $fields->addFieldToTab($tab, TabSet::create($main)); } } /** * Encode values into a JSON object and store the object in the database. * Includes a check to avoid repeated saves per cycle. */ public function onBeforeWrite() { parent::onBeforeWrite(); if ($this->hasEncoded) { return; } $this->encodeValues(); $this->hasEncoded = true; } /** * Use a custom ArrayData instance to expose a "forTemplate" approach * to rendering a setting in the template, while keeping the option * for accessing single variables explicitly. * * @param $me * @param $array * @return TemplateData */ protected function prepareTemplateData($me = null, $array = []) { $data = TemplateData::create($this->getColumnArray()); $data->setMe($this->toMeString()); return $data; } /** * Get a setting with the given name. * Throws if the setting does not exist. * * @param $name * @return mixed * @throws \Exception */ protected function getSetting($name) { $settings = $this->getSettings(); if (!isset($settings[$name])) { throw new \Exception('Setting "' . $name . '" does not exist on ' . $this->getTitle()); } return $settings[$name]; } /** * Get all available settings regardless of configuration. * * @return array */ protected function getAllSettings(): array { return static::config()->get('settings'); } /** * Get the actual settings as a result of the owner's declared constraints. * Fields may be overridden or excluded from the owner's config, in which case * this function respects that and returns only the defined settings. * * @return array */ protected function getSettings(): array { $settings = $this->getAllSettings(); $override = $this->getOwnerConfig('override', null); if (is_array($override)) { return $override; } $exclude = $this->getOwnerConfig('exclude'); if (($exclude && count($exclude))) { foreach ($exclude as $field) { unset($settings[$field]); } } return $settings; } protected function getTitle(): string { return static::config()->get('title') ?: $this->getColumn(); } protected function getColumn(): string { return static::config()->get('column'); } protected function getCssBase(): string { return static::config()->get('cssBase') ?: ''; } protected function getDefaultValue() { return $this->getOwnerConfig('defaultValue'); } protected function getTab() { $root = self::config()->get('rootTab'); $tab = static::config()->get('settingTab'); return "$root.$tab"; } /** * Get a unique name for the given setting to avoid CMS field naming collision. * * @param $setting * @return string */ protected function getNameFor($setting): string { return ClassInfo::shortName($this) . '_' . $setting; } /** * Get field type for the given setting. * * @param $setting * @return string */ protected function getFieldFor($setting): string { $value = $this->getSetting($setting); if (is_array($value)) { return $value['field']; } return $value; } /** * Get the front end label for the given setting. * * @param $setting * @return string */ protected function getLabelFor($setting): string { $value = $this->getSetting($setting); if (is_array($value)) { return $value['label'] ?? $setting; } return $setting; } /** * Get source for the given setting's field. * The source is set for every field, regardless of its relevance. * * @param $setting * @return array * @throws \Exception */ protected function getSourceFor($setting): array { $source = $this->getSetting($setting)['source'] ?? []; if ($source && count($source)) { return $source; } return static::config()->get('fallbackSource'); } /** * Get the current value for the given setting. * If there is no value for the setting, return the default value. * If no default value is set for the given setting, * attempt to get the first value of the source. * * @param $setting * @return mixed */ protected function getValueFor($setting) { $values = $this->getColumnArray(); $value = $values[$setting] ?? null; if ($value === null) { // If no value was set, find the most sensible default value. $default = $this->getDefaultValue(); if (is_array($default) && isset($default[$setting])) { // If the default config is an array, // index to the respective default value. $value = $default[$setting]; } else { // Otherwise, assign the default value. $value = $default; } if (!$value) { // Derive the first value in the source // in case no other default value could be found. $source = $this->getSourceFor($setting); $keys = array_keys($source); $value = $keys[0] ?? null; } } return $value; } /** * Get the complex value for this setting extension as an array. * Returns an empty array if no value has been set in the field. * * @return array */ protected function getColumnArray(): array { return $this->decodeValues(true) ?? []; } /** * Get the complex value for this setting extension as an object. * Returns a new stdClass instance if no value has been set in the field. * * @return \stdClass */ protected function getColumnObject(): \stdClass { return $this->decodeValues() ?? new \stdClass(); } /** * Get a ViewableData instance with mapped settings values. * * @return ViewableData */ protected function getViewableColumnData(): ViewableData { $values = $this->getColumnArray(); $data = []; foreach ($this->getSettings() as $setting => $_) { $data[$setting] = $values[$setting]; } return ArrayData::create($data); } /** * Generate a string that represents this object, * which can be useful as a css class name * * @return string */ protected function toMeString() { $string = ''; $base = $this->getCssBase(); $base = ($base ? "$base-" : ''); foreach ($this->getColumnArray() as $field => $value) { $string .= "$base$field-$value "; } return strtolower($string); } /** * Get the given setting from the column. * If not value is specified, it will be assumed that the column name * also represents the only possible value available. * * @param string $name * @param string $fallback * @return mixed|string */ protected function getColumnValue($name = '') { $name = $name ?: $this->getColumn(); $values = $this->getColumnArray(); return $values[$name] ?? $this->getDefaultValue(); } /** * Set the setting's value in the respective column. * * @param $value */ protected function setColumnValues($values) { $this->owner->{$this->getColumn()} = json_encode($values); } /** * Get the config for the owner of this setting. * Settings must adhere to a structured constraint configuration, * which is set on its owner. * * @param $field * @param array $fallback * @return array */ protected function getOwnerConfig($field, $fallback = []) { $class = static::class; $owner = $this->owner->ClassName; $template = $this->owner->Style; $config = new Config_ForClass($owner); // if (static::class === HeaderTagSetting::class && $field === 'defaultValue') { // echo '<pre>'; //// print_r(); // print_r($config->uninherited('styles')[$template][$field][$class] ?? $config->get($field)[static::class]); // echo '</pre>'; // //// echo '<pre>'; //// print_r($config->uninherited('styles')); //// echo '</pre>'; // die(); // } // Get the default styles for the respective nested template selection. return $config->uninherited('styles')[$template][$field][$class] ?? // If there was no template-specific config, look for one for the model. $config->get($field)[static::class] ?? // Use supplied fallback if no config could be found. $fallback; } /** * Decode the JSON values in the setting's column. * * @param bool $assoc * @return mixed */ protected function decodeValues($assoc = false) { return json_decode($this->owner->{$this->getColumn()}, $assoc); } /** * Encode and store the setting's values as JSON in the respective column. */ protected function encodeValues(): void { $owner = $this->owner; $values = []; foreach ($this->getSettings() as $setting => $_) { $name = $this->getNameFor($setting); if ($value = $owner->{$name}) { $values[$setting] = $value; } } $this->setColumnValues($values); } } |