diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2229f7c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{vue,js,scss}] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f0cec89 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.DS_Store +/vendor +/coverage +sftp-config.json +composer.lock +.subsplit +.php_cs.cache +.idea +/index.php +/config.php \ No newline at end of file diff --git a/.php_cs b/.php_cs new file mode 100644 index 0000000..48c7cf8 --- /dev/null +++ b/.php_cs @@ -0,0 +1,18 @@ +setRiskyAllowed(true) + ->setRules(array( + '@Symfony' => true, + 'array_syntax' => array('syntax' => 'short'), + 'ordered_imports' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'php_unit_construct' => true, + 'php_unit_strict' => true, + )) + ->setFinder( + PhpCsFixer\Finder::create() + ->exclude('vendor') + ->in(__DIR__) + ) +; \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..649677f --- /dev/null +++ b/README.md @@ -0,0 +1,202 @@ +Target Settings For Yii2 +====================== +Target Settings For Yii2 + +[![Latest Stable Version](https://poser.pugx.org/yiier/yii2-target-setting/v/stable)](https://packagist.org/packages/yiier/yii2-target-setting) +[![Total Downloads](https://poser.pugx.org/yiier/yii2-target-setting/downloads)](https://packagist.org/packages/yiier/yii2-target-setting) +[![Latest Unstable Version](https://poser.pugx.org/yiier/yii2-target-setting/v/unstable)](https://packagist.org/packages/yiier/yii2-target-setting) +[![License](https://poser.pugx.org/yiier/yii2-target-setting/license)](https://packagist.org/packages/yiier/yii2-target-setting) + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +``` +php composer.phar require --prefer-dist yiier/yii2-target-setting "*" +``` + +or add + +``` +"yiier/yii2-target-setting": "*" +``` + +to the require section of your `composer.json` file. + + +Configuration +------ + +### Database Migrations + +Before usage this extension, we'll also need to prepare the database. + + +``` +php yii migrate --migrationPath=@yiier/targetSetting/migrations/ +``` + + + +### Module Setup + +To access the module, you need to configure the modules array in your application configuration: + +```php +'modules' => [ + 'targetSetting' => [ + 'class' => 'yiier\targetSetting\Module', + ], +], + +``` + + +Component Setup + +To use the Setting Component, you need to configure the components array in your application configuration: + +```php +'components' => [ + 'targetSetting' => [ + 'class' => 'yiier\targetSetting\TargetSetting', + ], +], +``` + +Usage +----- + +```php +targetSetting; + +$value = $setting->get('key'); +$value = $setting->get('key', User::tableName(), Yii::$app->user->id); + +$setting->set('key', 125.5); +$setting->set('key', 125.5, User::tableName(), Yii::$app->user->id); + +$setting->set('key', false, User::tableName(), Yii::$app->user->id, 'Not allowed Update Post'); +$setting->set('key', false, '', 0, 'Not allowed Update Post'); + +// Checking existence of setting +$setting->has('key'); +$setting->has('key', User::tableName(), Yii::$app->user->id); + +// Activates a setting +$setting->activate('key'); +$setting->activate('key', User::tableName(), Yii::$app->user->id); + +// Deactivates a setting +$setting->deactivate('key'); +$setting->deactivate('key', User::tableName(), Yii::$app->user->id); + +// Removes a setting +$setting->remove('key'); +$setting->remove('key', User::tableName(), Yii::$app->user->id); + +// Removes all settings +$setting->removeAll(); +$setting->removeAll(User::tableName(), Yii::$app->user->id); + +// Get's all values in the specific section. +$setting->getAllByTarget(User::tableName(),Yii::$app->user->id); + +$setting->invalidateCache(); // automatically called on set(), remove(); +$setting->invalidateCache(User::tableName()); // automatically called on set(), remove(); +``` + + +TargetSettingAction +----- + +To use a custom settings form, you can use the included `TargetSettingAction`. + +1. Create a model class with your validation rules. +2. Create an associated view with an `ActiveForm` containing all the settings you need. +3. Add `yiier\targetSetting\targetSettingAction` to the controller's actions. + +The settings will be stored in section taken from the form name, with the key being the field name. + +### Model: + +```php + 'Site Name', + 'siteDescription' => 'Site Description' + ]; + } + +} +``` + +### Views: + + +```php + 'site-settings-form']); ?> + +field($model, 'siteName') ?> +field($model, 'siteDescription') ?> + 'btn btn-success']) ?> + + + +``` + +### Controller: + +```php +public function actions() +{ + return [ + //.... + 'site-settings' => [ + 'class' => TargetSettingAction::class, + 'modelClass' => 'app\models\SiteForm', + //'scenario' => 'site', // Change if you want to re-use the model for multiple setting form. + //'targetType' => 'company', // By default use 'user' + //'targetId' => 1, // By default use \Yii::$app->user->id + 'viewName' => 'site-settings', // The form we need to render + 'successMessage' => '保存成功' + ], + //.... + ]; +} +``` + + +Reference +----- + +- [yii2mod/yii2-settings](https://github.com/yii2mod/yii2-settings) +- [phemellc/yii2-settings](https://github.com/phemellc/yii2-settings) diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..96101d2 --- /dev/null +++ b/composer.json @@ -0,0 +1,25 @@ +{ + "name": "yiier/yii2-target-setting", + "description": "Target Settings For Yii2", + "type": "yii2-extension", + "keywords": [ + "yii2", + "extension", + "setting" + ], + "license": "BSD-4-Clause", + "authors": [ + { + "name": "forecho", + "email": "caizhenghai@gmail.com" + } + ], + "require": { + "yiisoft/yii2": "~2.0.0" + }, + "autoload": { + "psr-4": { + "yiier\\targetSetting\\": "src" + } + } +} diff --git a/src/Module.php b/src/Module.php new file mode 100644 index 0000000..edaf13b --- /dev/null +++ b/src/Module.php @@ -0,0 +1,25 @@ + + * createTime : 2019-07-27 12:55 + * description: + */ + +namespace yiier\userSetting; + + +class Module extends \yii\base\Module +{ + /** + * @var string The controller namespace to use + */ + public $controllerNamespace = 'yiier\targetSetting\controllers'; + + /** + * Init module + */ + public function init() + { + parent::init(); + } +} diff --git a/src/TargetSetting.php b/src/TargetSetting.php new file mode 100644 index 0000000..32eb63e --- /dev/null +++ b/src/TargetSetting.php @@ -0,0 +1,231 @@ + + * createTime : 2019-07-27 11:08 + * description: + */ + +namespace yiier\targetSetting; + +use Yii; +use yii\caching\Cache; +use yii\di\Instance; +use yii\helpers\ArrayHelper; + +class TargetSetting extends \yii\base\Component +{ + /** + * @var string setting model class name + */ + public $modelClass = 'yiier\targetSetting\models\TargetSettingModel'; + /** + * @var Cache|array|string the cache used to improve RBAC performance. This can be one of the followings: + * + * - an application component ID (e.g. `cache`) + * - a configuration array + * - a [[yii\caching\Cache]] object + * + * When this is not set, it means caching is not enabled + */ + public $cache = 'cache'; + /** + * @var string the key used to store settings data in cache + */ + public $cacheKey = 'yiier-target-setting'; + /** + * @var \yiier\targetSetting\models\TargetSettingModel setting model + */ + protected $model; + /** + * @var array list of settings + */ + protected $items; + /** + * @var mixed setting value + */ + protected $setting; + + /** + * Initialize the component + * @throws \yii\base\InvalidConfigException + */ + public function init() + { + parent::init(); + if ($this->cache !== null) { + $this->cache = Instance::ensure($this->cache, Cache::class); + } + $this->model = Yii::createObject($this->modelClass); + } + + /** + * Get's all values by target + * + * @param string $targetType + * @param int $targetId + * @param null $default + * + * @return mixed + */ + public function getAllByTarget($targetType = '', $targetId = 0, $default = null) + { + $items = $this->getSettingsConfig($targetType); + if (isset($items[$targetId])) { + $this->setting = ArrayHelper::getColumn($items[$targetId], 'value'); + } else { + $this->setting = $default; + } + return $this->setting; + } + + /** + * Get's the value for the key and target + * + * @param string $key + * @param string $targetType + * @param int $targetId + * @param null $default + * + * @return mixed + */ + public function get($key, $targetType = '', $targetId = 0, $default = null) + { + $items = $this->getSettingsConfig($targetType); + if (isset($items[$targetId][$key])) { + return ArrayHelper::getValue($items[$targetId][$key], 'value'); + } + return $default; + } + + /** + * Add a new setting or update an existing one. + * + * @param string $key + * @param string $value + * @param string $targetType + * @param int $targetId + * @param string $description + * + * @return bool + */ + public function set($key, $value, $targetType = '', $targetId = 0, $description = '') + { + if ($this->model->setSetting($key, $value, $targetType, $targetId, $description)) { + if ($this->invalidateCache($targetType)) { + return true; + } + } + return false; + } + + /** + * Checking existence of setting + * + * @param string $key + * @param string $targetType + * @param int $targetId + * @return bool + */ + public function has($key, $targetType = '', $targetId = 0) + { + $setting = $this->get($key, $targetType, $targetId); + return !empty($setting); + } + + /** + * Remove setting by target and key + * + * @param string $key + * @param string $targetType + * @param int $targetId + * @return bool + * @throws \Throwable + * @throws \yii\db\StaleObjectException + */ + public function remove($key, $targetType = '', $targetId = 0) + { + if ($this->model->removeSetting($key, $targetType, $targetId)) { + if ($this->invalidateCache($targetType)) { + return true; + } + } + return false; + } + + /** + * Remove all settings + * + * @param string $targetType + * @param int $targetId + * @return int + */ + public function removeAll($targetType = '', $targetId = 0) + { + return $this->model->removeAllSettings($targetType, $targetId); + } + + /** + * Activates a setting + * + * @param string $key + * @param string $targetType + * @param int $targetId + * @return bool + */ + public function activate($key, $targetType = '', $targetId = 0) + { + return $this->model->activateSetting($key, $targetType, $targetId); + } + + /** + * Deactivates a setting + * + * @param string $key + * @param string $targetType + * @param int $targetId + * @return bool + */ + public function deactivate($key, $targetType = '', $targetId = 0) + { + return $this->model->deactivateSetting($key, $targetType, $targetId); + } + + /** + * Returns the settings config + * + * @param string $targetType + * @return array + */ + protected function getSettingsConfig($targetType = '') + { + $cacheKey = $this->cacheKey . $targetType; + if (!$this->cache instanceof Cache) { + $this->items = $this->model->getSettings($targetType); + } else { + $cacheItems = $this->cache->get($cacheKey); + if (!empty($cacheItems)) { + $this->items = $cacheItems; + } else { + $this->items = $this->model->getSettings($targetType); + $this->cache->set($cacheKey, $this->items); + } + } + return $this->items; + } + + /** + * Invalidate the cache + * + * @param string $targetType + * @return bool + */ + public function invalidateCache($targetType = '') + { + if ($this->cache !== null) { + $cacheKey = $this->cacheKey . $targetType; + $this->cache->delete($cacheKey); + $this->items = null; + } + return true; + } +} diff --git a/src/TargetSettingAction.php b/src/TargetSettingAction.php new file mode 100644 index 0000000..5138749 --- /dev/null +++ b/src/TargetSettingAction.php @@ -0,0 +1,66 @@ + + * createTime : 2019-07-27 12:53 + * description: + */ + +namespace yiier\targetSetting; + +use Yii; +use yii\base\Action; + +class TargetSettingAction extends Action +{ + /** + * @var string class name of the model which will be used to validate the attributes. + * The class should have a scenario matching the `scenario` variable. + * The model class must implement [[Model]]. + * This property must be set. + */ + public $modelClass; + /** + * @var string The scenario this model should use to make validation + */ + public $scenario; + /** + * @var string the name of the view to generate the form. Defaults to 'setting'. + */ + public $viewName = 'target-setting'; + + /** + * @var string target id. Default is user + */ + public $targetType = 'user'; + + /** + * @var int target id. Default Yii::$app->user->id + */ + public $targetId = 0; + + public $successMessage = 'Successfully saved setting'; + + /** + * Render the setting form. + */ + public function run() + { + /* @var $model \yii\db\ActiveRecord */ + $model = new $this->modelClass(); + $targetId = ($this->targetId ?: Yii::$app->user->id); + $targetType = $this->targetType; + if ($this->scenario) { + $model->setScenario($this->scenario); + } + if ($model->load(Yii::$app->request->post()) && $model->validate()) { + foreach ($model->toArray() as $key => $value) { + Yii::$app->targetSetting->set($key, $value, $targetType, $targetId, $model->getAttributeLabel($key)); + } + Yii::$app->getSession()->addFlash('success', Yii::t('app', $this->successMessage)); + } + foreach ($model->attributes() as $key) { + $model->{$key} = Yii::$app->targetSetting->get($key, $targetType, $targetId); + } + return $this->controller->render($this->viewName, ['model' => $model]); + } +} diff --git a/src/migrations/m190727_023358_create_target_setting_tables.php b/src/migrations/m190727_023358_create_target_setting_tables.php new file mode 100644 index 0000000..cbe1957 --- /dev/null +++ b/src/migrations/m190727_023358_create_target_setting_tables.php @@ -0,0 +1,43 @@ +createTable($this->tableName, [ + 'id' => $this->primaryKey(), + 'type' => $this->string(20)->notNull(), // group、text、select、password、 + 'target_type' => $this->string(60)->notNull()->defaultValue(''), + 'target_id' => $this->integer()->notNull()->defaultValue(0), + 'key' => $this->string(60)->notNull(), + 'value' => $this->text(), + 'description' => $this->string(), + 'status' => $this->tinyInteger()->defaultValue(1), + 'created_at' => $this->integer(), + 'updated_at' => $this->integer(), + ], $tableOptions); + // Indexes + $this->createIndex('fk_target_type_target_id_key', $this->tableName, ['target_type', 'target_id', 'key'], true); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + $this->dropTable($this->tableName); + return true; + } +} diff --git a/src/models/TargetSettingModel.php b/src/models/TargetSettingModel.php new file mode 100644 index 0000000..a40b5bd --- /dev/null +++ b/src/models/TargetSettingModel.php @@ -0,0 +1,239 @@ + + * createTime : 2019-07-27 11:08 + * description: + */ + +namespace yiier\targetSetting\models; + +use Yii; +use yii\behaviors\TimestampBehavior; +use yii\helpers\ArrayHelper; + +/** + * This is the model class for table "{{%target_setting}}". + * + * @property int $id + * @property string $type + * @property string $target_type + * @property int $target_id + * @property string $key + * @property string $value + * @property string $description + * @property int $status + * @property int $created_at + * @property int $updated_at + */ +class TargetSettingModel extends \yii\db\ActiveRecord +{ + const STATUS_ACTIVE = 1; + const STATUS_INACTIVE = 0; + + /** + * {@inheritdoc} + */ + public static function tableName() + { + return '{{%target_setting}}'; + } + + /** + * @inheritdoc + */ + public function behaviors() + { + return [ + TimestampBehavior::class, + ]; + } + + + /** + * {@inheritdoc} + */ + public function rules() + { + return [ + [['type', 'key'], 'required'], + [['target_id', 'status', 'created_at', 'updated_at'], 'integer'], + [['value'], 'string'], + [['type'], 'string', 'max' => 20], + [['key', 'target_type'], 'string', 'max' => 60], + [['description'], 'string', 'max' => 255], + [['target_id', 'key'], 'unique', 'targetAttribute' => ['target_type', 'target_id', 'key']], + ]; + } + + /** + * {@inheritdoc} + */ + public function attributeLabels() + { + return [ + 'id' => Yii::t('app', 'ID'), + 'type' => Yii::t('app', 'Type'), + 'target_type' => Yii::t('app', 'Target Type'), + 'target_id' => Yii::t('app', 'Target ID'), + 'key' => Yii::t('app', 'Key'), + 'value' => Yii::t('app', 'Value'), + 'description' => Yii::t('app', 'Description'), + 'status' => Yii::t('app', 'Status'), + 'created_at' => Yii::t('app', 'Created At'), + 'updated_at' => Yii::t('app', 'Updated At'), + ]; + } + + + /** + * Creates an [[ActiveQueryInterface]] instance for query purpose. + * + * @return TargetSettingQuery + */ + public static function find(): TargetSettingQuery + { + return new TargetSettingQuery(get_called_class()); + } + + /** + * @inheritdoc + */ + public function afterDelete() + { + parent::afterDelete(); + Yii::$app->targetSetting->invalidateCache(); + } + + /** + * @inheritdoc + */ + public function afterSave($insert, $changedAttributes) + { + parent::afterSave($insert, $changedAttributes); + Yii::$app->targetSetting->invalidateCache(); + } + + /** + * Return array of settings + * + * @param string $targetType + * @return array + */ + public function getSettings($targetType = '') + { + $result = []; + $settings = static::find()->where(['target_type' => $targetType])->active()->asArray()->all(); + foreach ($settings as $setting) { + $targetId = $setting['target_id']; + $key = $setting['key']; + $settingOptions = [ + 'type' => $setting['type'], + 'value' => $setting['value'], + 'description' => $setting['description'] + ]; + if (isset($result[$targetId][$key])) { + ArrayHelper::merge($result[$targetId][$key], $settingOptions); + } else { + $result[$targetId][$key] = $settingOptions; + } + } + return $result; + } + + /** + * Set setting + * + * @param $key + * @param $value + * @param string $targetType + * @param int $targetId + * @param string $description + * @return bool + */ + public function setSetting($key, $value, $targetType = '', $targetId = 0, $description = '') + { + $conditions = ['target_type' => $targetType, 'target_id' => $targetId, 'key' => $key]; + if (!$model = static::find()->where($conditions)->limit(1)->one()) { + $model = new static(); + } + $model->target_type = $targetType; + $model->target_id = $targetId; + $model->key = $key; + $model->value = strval($value); + $model->description = strval($description); + $model->type = gettype($value); + return $model->save(); + } + + /** + * Remove setting + * + * @param $key + * @param string $targetType + * @param int $targetId + * @return bool|int|null + * + * @throws \Throwable + * @throws \yii\db\StaleObjectException + */ + public function removeSetting($key, $targetType = '', $targetId = 0) + { + $conditions = ['target_type' => $targetType, 'target_id' => $targetId, 'key' => $key]; + if (!$model = static::find()->where($conditions)->limit(1)->one()) { + return $model->delete(); + } + return false; + } + + /** + * Remove all settings + * + * @param string $targetType + * @param int $targetId + * @return int + */ + public function removeAllSettings($targetType = '', $targetId = 0) + { + return static::deleteAll(['target_type' => $targetType, 'target_id' => $targetId]); + } + + /** + * Activates a setting + * + * @param $key + * + * @param string $targetType + * @param int $targetId + * @return bool + */ + public function activateSetting($key, $targetType = '', $targetId = 0) + { + $conditions = ['target_type' => $targetType, 'target_id' => $targetId, 'key' => $key]; + $model = static::find()->where($conditions)->limit(1)->one(); + if ($model && $model->status === self::STATUS_INACTIVE) { + $model->status = self::STATUS_ACTIVE; + return $model->save(true, ['status']); + } + return false; + } + + /** + * Deactivates a setting + * + * @param $key + * + * @param string $targetType + * @param int $targetId + * @return bool + */ + public function deactivateSetting($key, $targetType = '', $targetId = 0) + { + $conditions = ['target_type' => $targetType, 'target_id' => $targetId, 'key' => $key]; + $model = static::find()->where($conditions)->limit(1)->one(); + if ($model && $model->status === self::STATUS_ACTIVE) { + $model->status = self::STATUS_INACTIVE; + return $model->save(true, ['status']); + } + return false; + } +} diff --git a/src/models/TargetSettingQuery.php b/src/models/TargetSettingQuery.php new file mode 100644 index 0000000..ab75f52 --- /dev/null +++ b/src/models/TargetSettingQuery.php @@ -0,0 +1,34 @@ + + * createTime : 2019-07-27 11:21 + * description: + */ + +namespace yiier\targetSetting\models; + + +use yii\db\ActiveQuery; + +class TargetSettingQuery extends ActiveQuery +{ + /** + * Scope for settings with active status + * + * @return $this + */ + public function active() + { + return $this->andWhere(['status' => TargetSettingModel::STATUS_ACTIVE]); + } + + /** + * Scope for settings with inactive status + * + * @return $this + */ + public function inactive() + { + return $this->andWhere(['status' => TargetSettingModel::STATUS_ACTIVE]); + } +}