. */ namespace App\Helpers; use App\Enums\OverridableFeatures; use App\Enums\Overrides; use App\Exceptions\EmptyOptionsException; use App\Exceptions\OptionNotFoundException; use App\Options as Option; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Log; use LogicException; class Options { /** * Returns an assortment of settings found in the mentioned category * * @param string $category The category * @return Collection The settings in this category * * @throws EmptyOptionsException */ public function getCategory(string $category): Collection { $options = Option::where('option_category', $category)->get(); if ($options->isEmpty()) { throw new EmptyOptionsException('There are no options in category '.$category); } return $options; } public function getOption(string $option): string { $value = Cache::get($option); if (is_null($value)) { Log::debug('Option '.$option.'not found in cache, refreshing from database'); $value = Option::where('option_name', $option)->first(); if (is_null($value)) { throw new OptionNotFoundException('This option does not exist.'); } Cache::put($option, $value->option_value); Cache::put($option.'_desc', 'Undefined description'); return $this->modifyResponse($option, $value->option_value); } return $this->modifyResponse($option, $value); } public function isFeature(string $optionName): bool { if ($this->getCategory('app_features')->contains('option_name', $optionName)) { return true; } return false; } /** * CHeck if given option is locallu overriden * * @param string $option Option to check * @throws LogicException Thrown when feature is not locally overridable, e.g. not defined in the settings file * @return bool */ public function isOverriden(string $option): bool { if (!array_key_exists($option, config('local-overrides.features'))) { throw new LogicException('Feature does not exist or is not locally overridable'); } $feature = config("local-overrides.features.$option"); return ($feature == Overrides::forceEnable->value || $feature == Overrides::killSwitch->value); } /** * Defines an option to store in the database. * * @param string $option Option name. * @param string $value Option value. * @param string $description Description for this, can be null * @param string|null $category Option category * @return void */ public function setOption(string $option, string $value, string $description, string $category = null) { Option::create([ 'option_name' => $option, 'option_value' => $value, 'friendly_name' => $description, 'option_category' => $category, ]); Cache::put($option, $value, now()->addDay()); Cache::put($option.'_desc', $description, now()->addDay()); } /** * Gets and deletes option * * @param string $option Option to pull * @return array */ public function pullOption($option): array { $oldOption = Option::where('option_name', $option)->first(); Option::find($oldOption->id)->delete(); // putMany is overkill here return [ Cache::pull($option), Cache::pull($option.'_desc'), ]; } /** * Updates an option * * @param $option * @param $newValue * @return void * @throws OptionNotFoundException */ public function changeOption($option, $newValue) { $dbOption = Option::where('option_name', $option); if ($dbOption->first()) { $dbOptionInstance = Option::find($dbOption->first()->id); Cache::forget($option); Log::debug('Changing db configuration option', [ 'old_value' => $dbOptionInstance->option_value, 'new_value' => $newValue, ]); $dbOptionInstance->option_value = $newValue; $dbOptionInstance->save(); Log::debug('New db configuration option saved', [ 'option' => $dbOptionInstance->option_value, ]); Cache::put('option_name', $newValue, now()->addDay()); } else { throw new OptionNotFoundException('This option does not exist.'); } } /** * Check if option exists * * @param string $option * @return bool */ public function optionExists(string $option): bool { $dbOption = Option::where('option_name', $option)->first(); $locallyCachedOption = Cache::get($option); return ! is_null($dbOption) || ! is_null($locallyCachedOption); } /** * Get the actual status for the override in question * * @param string $option * @return bool|Overrides */ public function getOverride(string $option): bool|string { return config("local-overrides.features.$option"); } /** * Modifies the outgoing option value according to its override value * * @param string $option * @param int|string $value Setting value * @return int Modified setting value accordidng to business rules */ private function modifyResponse(string $option, int|string $value): int|string { // TODO: This method should handle bools (feature on/off), we need to move this to getOption, but only after its been refactored. (this is a quick fix) if (!$this->isFeature($option)) { return $value; } $modifiedValue = $value; try { if ($this->isOverriden($option) && $this->getOverride($option) == Overrides::forceEnable->value) { $modifiedValue = Overrides::forceEnable->value; } if ($this->isOverriden($option) && $this->getOverride($option) == Overrides::killSwitch->value) { $modifiedValue = 0; } } catch (LogicException $exception) { Log::debug('Illegal attempt to modify setting value: not overridable; not modifying', [ 'msg' => $exception->getMessage() ]); return $value; } return $modifiedValue; } }