├── LICENSE ├── README.md ├── Wc.php ├── behaviors ├── CatchMessageBehavior.php ├── CheckLanguageBehavior.php ├── DispatchBehavior.php ├── ModifyTimestampBehavior.php └── TreeBehavior.php ├── composer.json ├── core ├── BaseWc.php ├── ConfigProvider.php ├── ControllerInfo.php ├── DbConfigProvider.php ├── Dispatch.php ├── DispatchManager.php ├── ExtensionInfo.php ├── MenuProvider.php ├── Model.php ├── Modularity.php ├── ModularityInfo.php ├── Service.php ├── ServiceLocator.php ├── Theme.php ├── ThemeInfo.php ├── WoCenterExtension.php └── web │ ├── Controller.php │ └── Dispatch.php ├── db ├── ActiveRecord.php └── Migration.php ├── dispatches ├── BaseDelete.php ├── DeleteOne.php └── MultipleDelete.php ├── enums ├── EnableEnum.php ├── Enums.php ├── GenderEnum.php ├── TargetEnum.php ├── VisibleEnum.php └── YesEnum.php ├── filters └── LogActionTime.php ├── grid └── DateTimeColumn.php ├── helpers ├── ArrayHelper.php ├── CacheManagerHelper.php ├── ConsoleHelper.php ├── DateTimeHelper.php ├── FileHelper.php ├── SecurityHelper.php ├── StringHelper.php ├── TreeHelper.php └── UrlHelper.php ├── interfaces ├── ConfigProviderInterface.php ├── ControllerInfoInterface.php ├── ControllerRepositoryInterface.php ├── DispatchManagerInterface.php ├── EnumInterface.php ├── ExtensionInfoInterface.php ├── ExtensionRepositoryInterface.php ├── MenuProviderInterface.php ├── ModularityInfoInterface.php ├── ModularityRepositoryInterface.php ├── RunningExtensionInterface.php ├── ServiceInterface.php ├── ThemeInfoInterface.php └── ThemeRepositoryInterface.php ├── libs ├── IpLocation.php ├── PinYin.php ├── UTFWry.dat └── Utils.php ├── messages └── zh-CN │ └── app.php ├── models └── BaseConfigModel.php ├── traits ├── DispatchShortcutTrait.php ├── DispatchTrait.php ├── ExtendModelTrait.php ├── ExtensionTrait.php ├── LoadModelTrait.php └── ParseRulesTrait.php ├── validators └── DateControlValidator.php ├── views ├── _issue-message.php └── template.php └── widgets ├── DateControl.php ├── DateTimePicker.php ├── FlashAlert.php └── Issue.php /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2016, Wonail 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WoCenter 介绍 2 | ------------- 3 | 4 | > WoCenter是基于php Yii2开发的一款优秀的**易扩展**和**高可定制**的开源框架。 5 | 6 | WoCenter在设计之初就非常重视二次开发的**便捷性**、**易用性**和**低干扰性**。WoCenter致力于解决这一问题, 7 | 为开发者提供一个省心好用且对二次开发更友好的底层框架,在保留Yii2原有操作习惯的基础上,开发出一系列WoCenter特有的特性, 8 | 如**Service服务层**、**Dispatch调度层**和**扩展机制**等,**而易于扩展和重构的系统设计有助于你定制出称心如意的项目**。 9 | 10 | WoCenter遵循[BSD-3-Clause协议](https://github.com/Wonail/wocenter/blob/master/LICENSE),意味着你可以免费的部署你的线上项目。 11 | 12 | WoCenter 作者微信:234251232 13 | 14 | WoCenter QQ群:573142468 15 | 16 | WoCenter Github地址: https://github.com/Wonail/wocenter.git 17 | 18 | 推荐项目 19 | ------------- 20 | 21 | [WoCenter Advanced](https://github.com/Wonail/wocenter_advanced)是基于WoCenter开发的一款优秀的**高度可扩展**的高级项目模板。 22 | 23 | > WoCenter Advanced充分利用和发挥了WoCenter的所有特性,基于WoCenter的**扩展机制**, 24 | 系统核心模块和默认主题均使用扩展中心提供的扩展插件来满足系统的高定制化需求。你可以根据需要自由开发私有或公有的扩展, 25 | 也可以使用其他开发者的扩展来定制你的业务系统,避免重复造轮子,开箱即用。 26 | 27 | WoCenter 文档 28 | ------------- 29 | 30 | **权威指南:** [WoCenter 权威指南](http://www.wonail.com/doc/wocenter/guide/zh-CN/guide-index.html) 31 | 32 | 架构特色 33 | ----------- 34 | 35 | - Service服务层 36 | 37 | Service服务层的目的在于进一步解耦Model层,让Model层只专注于CRUD、规则验证、数据库映射等简单操作, 38 | **降低Model和业务逻辑的耦合性**,方便日后业务发展可灵活定制Model底层, 39 | 而其余复杂的业务逻辑则交由Service层为系统提供实际的功能使用接口。 40 | 41 | - Dispatch调度层 42 | 43 | 传统的MVC设计模式中,`C`(Controller)负责响应路由请求并从所需的`M`(Model)中获取数据接着由`V`(View)渲染所需的视图文件, 44 | 每个层级各司其职,这是一种很好的设计模式,能够使各层级职能分配清楚,极大的解耦各层级的关联性而又不损害其所需的相关性。 45 | 46 | 然而,在WoCenter实际开发的过程中,在应对真正多主题或高个性化定制的情况下,传统设计模式并不能很好地满足需求, 47 | 故在传统设计模式中的`C`和`V`之间新增一个调度层(**Dispatch**,简称`D`),用以进一步解藕细分`C`, 48 | 同时为二次开发提供更高的**友好性**和**适用性**。 49 | 50 | 有时面对`C`复杂的操作设计,会导致`C`方法量或单个操作代码过多,这与瘦控制器胖模型的设计背道而驰, 51 | 而`D`则可以有效地把复杂的设计解藕分离出来,针对单个操作提供专属的`D`,实现一对一的关系,方便管理,同时起到瘦控制器的作用, 52 | 并可**使控制器与主题相关性不强**,满足系统较高的可定制化需求。 53 | 54 | - 扩展机制 55 | 56 | 目前提供了一套以主题、模块、功能等为分类的扩展规范,开发者按此规范可自由开发私有或公有的扩展, 57 | 也可以使用其他开发者的扩展来定制你的业务系统,而这一切的操作如同搭积木一样简单便捷, 58 | 开发者仅需简单进行**安装、卸载和升级**等操作即可完成。 59 | 60 | - 重写机制 61 | 62 | 得益于Yii2优秀的设计,使得开发者可以非常简单地对系统几乎所有核心文件进行重构或个性化修改(通过classMap类映射, 63 | 该方法主要是通过配置文件方式进行覆盖重写),而对于控制器操作、视图文件、资源文件等的修改,都可以更简单地通过**复制、粘贴、修改**来完成 64 | (无需任何配置),这一切的基础都源于WoCenter的**调度层**和**扩展机制**特性。 65 | 66 | - 低干扰 67 | 68 | WoCenter以composer包方式安装,故核心文件存放于`vendor/wonail/wocenter`路径,和开发者路径完全隔离, 69 | 使得系统后期的升级实现最小化干扰,几乎不对开发者有任何影响。而升级WoCenter核心系统方面,只需简单的`composer update`即可。 70 | 71 | - 完善的文档注释和IDE代码提示 72 | 73 | 系统核心功能做了大部分的文档注释以及对IDE的友好支持,很大程度上利于开发者的二次开发。 74 | -------------------------------------------------------------------------------- /Wc.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Wc extends BaseWc 13 | { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /behaviors/CatchMessageBehavior.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class CatchMessageBehavior extends Behavior 23 | { 24 | 25 | /** 26 | * @var string 模型内反馈信息 27 | */ 28 | public $_message = ''; 29 | 30 | /** 31 | * @inheritdoc 32 | */ 33 | public function events() 34 | { 35 | return [ 36 | Model::EVENT_AFTER_VALIDATE => 'afterValidate', 37 | ]; 38 | } 39 | 40 | /** 41 | * @throws Exception 42 | */ 43 | public function afterValidate() 44 | { 45 | if ($this->owner->hasErrors()) { 46 | // 格式化信息数组 47 | $errors = $this->owner->getFirstErrors(); 48 | if (count($errors) > 1) { 49 | $i = 1; 50 | foreach ($errors as &$value) { 51 | $value = $i++ . ') ' . $value; 52 | } 53 | } 54 | /** 55 | * 添加事务支持 56 | * 57 | * 如果存在事务操作且事务内的模型启用抛出异常,则把获取到的反馈信息以异常方式抛出, 58 | * 否则反馈信息由模型自行吞掉,此时可通过`$_message`属性获取相关反馈信息 59 | */ 60 | if (Yii::$app->getDb()->getIsActive() && $this->_throwException() 61 | ) { 62 | throw new Exception(ArrayHelper::arrayToString($errors, '')); 63 | } else { 64 | $this->_message = ArrayHelper::arrayToString($errors, "
"); 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * 是否允许抛出异常 71 | * 72 | * @return bool 73 | */ 74 | private function _throwException() 75 | { 76 | return $this->owner->getThrowException() === true 77 | || (Wc::getThrowException() && $this->owner->getThrowException() !== false); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /behaviors/CheckLanguageBehavior.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class CheckLanguageBehavior extends Behavior 16 | { 17 | 18 | public $languageParam = '_lang'; 19 | 20 | /** 21 | * @inheritdoc 22 | */ 23 | public function events() 24 | { 25 | return [ 26 | Application::EVENT_BEFORE_REQUEST => 'checkLanguage', 27 | ]; 28 | } 29 | 30 | public function checkLanguage() 31 | { 32 | $request = Yii::$app->getRequest(); 33 | if ($request->get($this->languageParam)) { 34 | // By passing a parameter to change the language 35 | Yii::$app->language = htmlspecialchars($request->get($this->languageParam)); 36 | 37 | // get the cookie collection (yii\web\CookieCollection) from the "response" component 38 | $cookies = Yii::$app->getResponse()->getCookies(); 39 | // add a new cookie to the response to be sent 40 | $cookies->add(new Cookie([ 41 | 'name' => $this->languageParam, 42 | 'value' => Yii::$app->language, 43 | 'expire' => time() + (365 * 24 * 60 * 60), 44 | ])); 45 | } elseif (isset($request->cookies[$this->languageParam]) && $request->cookies[$this->languageParam]->value != "") { 46 | // COOKIE in accordance with the language type to set the language 47 | Yii::$app->language = $request->cookies[$this->languageParam]->value; 48 | } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { 49 | // According to the browser language to set the language 50 | $lang = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); 51 | Yii::$app->language = $lang[0]; 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /behaviors/DispatchBehavior.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class DispatchBehavior extends Behavior 19 | { 20 | 21 | /** 22 | * @var Dispatch 23 | */ 24 | public $owner; 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | public function attach($owner) 30 | { 31 | if (!$owner instanceof Dispatch) { 32 | throw new InvalidConfigException('The owner of this behavior `' . self::class . '` must be instanceof ' . 33 | '`\wocenter\core\web\Dispatch`'); 34 | } 35 | parent::attach($owner); 36 | } 37 | 38 | /** 39 | * 操作成功后返回结果至客户端 40 | * 41 | * @param string $message 提示信息 42 | * @param string|array|null $jumpUrl 页面跳转地址。默认为`null`,即仅设置提示信息。 43 | * - string: 当为字符串时,则自动跳转到该地址,为空字符串时则跳转到当前请求地址。 44 | * - array: 数组形式的路由地址。 45 | * - null: 不需要跳转。 46 | * @param mixed $data 返回给客户端的数据。 47 | * 该值设置建议遵照以下原则: 48 | * - 为整数,代表页面跳转停留时间,默认为1妙,时间结束后自动跳转至指定的`$jumpUrl`页面。 49 | * - 为数组,代表返回给客户端的数据。 50 | * 51 | * @return Response 52 | */ 53 | public function success($message = '', $jumpUrl = null, $data = []): Response 54 | { 55 | return $this->_dispatchJump($message ?: Yii::t('wocenter/app', 'Operation successful.'), 1, $jumpUrl, $data); 56 | } 57 | 58 | /** 59 | * 操作失败后返回结果至客户端 60 | * 61 | * @param string $message 提示信息 62 | * @param string|array|null $jumpUrl 页面跳转地址。默认为`null`,即仅设置提示信息。 63 | * - string: 当为字符串时,则自动跳转到该地址,为空字符串时则跳转到当前请求地址。 64 | * - array: 数组形式的路由地址。 65 | * - null: 不需要跳转。 66 | * @param mixed $data 返回给客户端的数据。 67 | * 该值设置建议遵照以下原则: 68 | * - 为整数,代表页面跳转停留时间,默认为3妙,时间结束后自动跳转至指定的`$jumpUrl`页面。 69 | * - 为数组,代表返回给客户端的数据。 70 | * 71 | * @return Response 72 | */ 73 | public function error($message = '', $jumpUrl = null, $data = []): Response 74 | { 75 | return $this->_dispatchJump($message ?: Yii::t('wocenter/app', 'Operation failure.'), 0, $jumpUrl, $data); 76 | } 77 | 78 | /** 79 | * 处理跳转操作,支持错误跳转和正确跳转。 80 | * 主要是把结果数据以json形式返回给前端,一般用来处理AJAX请求 81 | * 82 | * @param string|array $message 提示信息 83 | * @param integer $status 状态 1:success 0:error 84 | * @param string|array|null $jumpUrl 页面跳转地址。默认为`null`,即仅设置提示信息。 85 | * - string: 当为字符串时,则自动跳转到该地址,为空字符串时则跳转到当前请求地址。 86 | * - array: 数组形式的路由地址。 87 | * - null: 不需要跳转。 88 | * @param array|integer $data 89 | * 90 | * @return Response 默认都会返回包含以下键名的数据到客户端 91 | * ```php 92 | * [ 93 | * 'waitSecond', 94 | * 'jumpUrl', 95 | * 'message', 96 | * 'status', 97 | * ..., 98 | * ] 99 | * ``` 100 | */ 101 | protected function _dispatchJump($message = '', $status = 1, $jumpUrl = null, $data = []): Response 102 | { 103 | $request = Yii::$app->getRequest(); 104 | // 设置跳转时间 105 | if (is_int($data)) { 106 | $params['waitSecond'] = $data; 107 | } elseif (is_array($data)) { 108 | $params = $data; 109 | if (!isset($params['waitSecond'])) { 110 | $params['waitSecond'] = $status ? 1 : 3; 111 | } 112 | } 113 | $params['jumpUrl'] = $jumpUrl === null ? '' : Url::to($jumpUrl); 114 | $params['status'] = $status; 115 | $params['message'] = $message; 116 | 117 | // 全页面加载 118 | if ($request->getIsPjax() || !$request->getIsAjax()) { 119 | Yii::$app->getSession()->setFlash($status ? 'success' : 'error', $params['message']); 120 | if (!empty($params['jumpUrl'])) { 121 | $this->owner->controller->redirect($params['jumpUrl']); 122 | Yii::$app->end(); 123 | } else { 124 | $this->setAssign($params); 125 | } 126 | } // AJAX请求方式 127 | else { 128 | $this->owner->controller->asJson($params); 129 | Yii::$app->end(); 130 | } 131 | 132 | return Yii::$app->getResponse(); 133 | } 134 | 135 | /** 136 | * 显示页面 137 | * 138 | * @param string|null $view 需要渲染的视图文件名 139 | * @param array $assign 视图模板赋值数据 140 | * 141 | * @return string 142 | */ 143 | public function display($view = null, array $assign = []) 144 | { 145 | return $this->setAssign($assign)->controller->render( 146 | $view ?: $this->owner->controller->getDispatchManager()->getViewPath(), 147 | $this->getAssign() 148 | ); 149 | } 150 | 151 | /** 152 | * @var array 保存视图模板文件赋值数据 153 | */ 154 | protected $_assign = []; 155 | 156 | /** 157 | * 设置视图模板文件赋值数据 158 | * 159 | * 示例: 160 | * ```php 161 | * $this->setAssign('name1', 'apple'); 162 | * $this->setAssign('name2', 'orange'); 163 | * 等于 164 | * $this->setAssign([ 165 | * 'name1' => 'apple', 166 | * 'name2' => 'orange' 167 | * ]); 168 | * 169 | * ``` 170 | * 171 | * 使用该方法可向当前控制器的调度器内传入公共模板数据 172 | * 173 | * @param string|array $key 174 | * @param mixed $value 175 | * 176 | * @return Dispatch 177 | */ 178 | final public function setAssign($key, $value = null) 179 | { 180 | if (is_array($key)) { 181 | $this->_assign = ArrayHelper::merge($this->_assign, $key); 182 | } else { 183 | $this->_assign[$key] = $value; 184 | } 185 | 186 | return $this->owner; 187 | } 188 | 189 | /** 190 | * 获取视图模板文件赋值数据 191 | * 192 | * @return array 193 | */ 194 | final public function getAssign() 195 | { 196 | return $this->_assign; 197 | } 198 | 199 | } -------------------------------------------------------------------------------- /behaviors/TreeBehavior.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class TreeBehavior extends Behavior 21 | { 22 | 23 | /** 24 | * @var Model|ActiveRecord|Component|null 25 | */ 26 | public $owner; 27 | 28 | /** 29 | * @var string 下拉选项需要显示的标题字段 30 | */ 31 | public $showTitleField = 'label'; 32 | 33 | /** 34 | * @var string 下拉选项的值字段 35 | */ 36 | public $showPkField = 'id'; 37 | 38 | /** 39 | * @var string 菜单主键字段,用于获取相关父级或子级菜单数据,通常是当前模型类的主键字段 40 | */ 41 | public $pkField = 'id'; 42 | 43 | /** 44 | * @var string 菜单父级字段 45 | */ 46 | public $parentField = 'parent_id'; 47 | 48 | /** 49 | * @var string TAB大小 50 | */ 51 | public $tabSize = "    "; 52 | 53 | /** 54 | * @var integer 菜单层级 55 | */ 56 | private $_level; 57 | 58 | /** 59 | * 获取菜单层级 60 | * 61 | * @return integer 62 | */ 63 | public function getLevel() 64 | { 65 | return $this->_level; 66 | } 67 | 68 | /** 69 | * 设置菜单层级 70 | * 71 | * @param integer $value 菜单层级 72 | */ 73 | public function setLevel($value) 74 | { 75 | $this->_level = $value; 76 | } 77 | 78 | /** 79 | * @inheritdoc 80 | */ 81 | public function attach($owner) 82 | { 83 | if (!($owner instanceof Model || $owner instanceof ActiveRecord)) { 84 | throw new InvalidConfigException('The owner of this behavior `' . self::class . '` must be instanceof ' . 85 | '`\wocenter\core\Model` or `\wocenter\db\ActiveRecord`'); 86 | } 87 | parent::attach($owner); 88 | } 89 | 90 | /** 91 | * 获取树型下拉选项列表 92 | * 93 | * @param array $list 树形菜单数据 94 | * 95 | * @return array 96 | */ 97 | public function getTreeSelectList(array $list): array 98 | { 99 | return $this->buildTreeOptions($this->_formatTree($list)); 100 | } 101 | 102 | /** 103 | * 生成树型下拉选项数据 104 | * 105 | * @param array $list 菜单数据 106 | * 107 | * @return array 108 | */ 109 | protected function buildTreeOptions($list) 110 | { 111 | $found = false; 112 | $options = []; 113 | /** @var self $model */ 114 | $model = !empty($this->owner->{$this->pkField}) ? $this->owner : null; 115 | // 模型数据存在时,排除自身选项 116 | foreach ($list as $row) { 117 | if ($model != null) { 118 | // 排除自身选项 119 | if ($model[$this->showPkField] == $row[$this->showPkField]) { 120 | $model->level = $row['level']; 121 | $found = true; 122 | continue; 123 | } 124 | // 排除归属自身的子类选项 125 | if ($found) { 126 | if ($row['level'] > $model->level) { 127 | continue; 128 | } else { 129 | $found = false; 130 | } 131 | } 132 | } 133 | 134 | $options[$row[$this->showPkField]] = $row[$this->showTitleField]; 135 | } 136 | 137 | return $options; 138 | } 139 | 140 | /** 141 | * 格式化菜单列表数据 142 | * 143 | * @param array $list 144 | * @param integer $level 145 | * 146 | * @return array 147 | */ 148 | private function _formatTree($list, $level = 0) 149 | { 150 | $formatTree = []; 151 | foreach ($list as $val) { 152 | $tmp_str = str_repeat($this->tabSize, $level); 153 | $tmp_str .= "└ "; 154 | $val['level'] = $val['level'] ?? intval($level); 155 | $val[$this->showTitleField] = ($level ? $tmp_str : '') . $val[$this->showTitleField]; 156 | if (!array_key_exists('items', $val)) { 157 | array_push($formatTree, $val); 158 | } else { 159 | $tmp_ary = $val['items']; 160 | unset($val['items']); 161 | array_push($formatTree, $val); 162 | $formatTree = array_merge($formatTree, $this->_formatTree($tmp_ary, $level + 1)); 163 | } 164 | } 165 | 166 | return $formatTree; 167 | } 168 | 169 | /** 170 | * @var array 父级ID数据 171 | */ 172 | private $_parentIds; 173 | 174 | /** 175 | * 获取当前模型所有父级ID数据 176 | * 177 | * @param integer $rootId 顶级ID,默认为`0`,返回的父级ID数据获取到此顶级ID后则停止获取 178 | * 179 | * @return array 180 | */ 181 | public function getParentIds($rootId = 0) 182 | { 183 | if ($this->_parentIds === null) { 184 | $this->_parentIds = TreeHelper::getParentIds($this->owner, 0, $this->pkField, $this->parentField, $rootId); 185 | } 186 | 187 | return $this->_parentIds; 188 | } 189 | 190 | /** 191 | * @var array 子类ID数据 192 | */ 193 | private $_childrenIds; 194 | 195 | /** 196 | * 获取当前模型所有子类ID数据 197 | * 198 | * @return array 199 | */ 200 | public function getChildrenIds() 201 | { 202 | if ($this->_childrenIds === null) { 203 | $this->_childrenIds = TreeHelper::getChildrenIds($this->owner, $this->owner->{$this->pkField}, $this->pkField, $this->parentField); 204 | } 205 | 206 | return $this->_childrenIds; 207 | } 208 | 209 | /** 210 | * @inheritdoc 211 | */ 212 | public function events() 213 | { 214 | return [ 215 | ActiveRecord::EVENT_BEFORE_DELETE => 'hasChildren', 216 | ActiveRecord::EVENT_BEFORE_UPDATE => 'isValidParentId', 217 | ]; 218 | } 219 | 220 | /** 221 | * 检测当前模型是否拥有子类数据 222 | * 223 | * @param ModelEvent $event 224 | */ 225 | public function hasChildren($event) 226 | { 227 | if (!empty($this->getChildrenIds())) { 228 | $this->owner->_message = Yii::t('wocenter/app', 'Please delete or move the child data under this data before deleting it.'); 229 | $event->isValid = false; 230 | } 231 | } 232 | 233 | /** 234 | * 检测当前模型的父级id是否有效,避免非法操作导致死循环 235 | * 236 | * @param ModelEvent $event 237 | */ 238 | public function isValidParentId($event) 239 | { 240 | if (in_array($this->owner->{$this->parentField}, $this->getChildrenIds())) { 241 | $this->owner->_message = Yii::t('wocenter/app', 'Parent id is invalid.'); 242 | $event->isValid = false; 243 | } 244 | } 245 | 246 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wonail/wocenter", 3 | "description": "基于php Yii2开发的一款优秀的易扩展和高可定制的开源框架", 4 | "keywords": [ 5 | "yii2", 6 | "bootstrap 3", 7 | "wocenter", 8 | "wonail" 9 | ], 10 | "authors": [ 11 | { 12 | "name": "E-Kevin", 13 | "email": "e-kevin@qq.com" 14 | } 15 | ], 16 | "type": "yii2-extension", 17 | "license": "BSD-3-Clause", 18 | "homepage": "https://github.com/Wonail/wocenter", 19 | "require": { 20 | "php": ">=7.0", 21 | "yiisoft/yii2": "~2.0.12", 22 | "kartik-v/yii2-datecontrol": "~1.9.6", 23 | "kartik-v/yii2-widget-datepicker": "@dev", 24 | "kartik-v/yii2-widget-timepicker": "@dev", 25 | "kartik-v/yii2-widget-datetimepicker": "@dev" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "wocenter\\": "" 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /core/BaseWc.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class BaseWc extends BaseObject 23 | { 24 | 25 | /** 26 | * @var ServiceLocator 服务类实例,用于调用系统服务 27 | */ 28 | public static $service; 29 | 30 | /** 31 | * Wc constructor. 32 | * 33 | * @param ServiceLocator $service 34 | * @param array $config 35 | * 36 | * @author E-Kevin 37 | */ 38 | public function __construct(ServiceLocator $service, $config = []) 39 | { 40 | static::$service = $service; 41 | 42 | parent::__construct($config); 43 | } 44 | 45 | /** 46 | * WoCenter 当前版本 47 | * 48 | * @return string 49 | */ 50 | public static function getVersion() 51 | { 52 | return '0.4'; 53 | } 54 | 55 | /** 56 | * 输出调试信息 57 | * 58 | * @param string|array $var 59 | * @param string $category 60 | */ 61 | public static function traceInfo($var, $category = 'Wc::traceInfo') 62 | { 63 | Yii::debug(VarDumper::dumpAsString($var), $category); 64 | } 65 | 66 | /** 67 | * 浏览器友好的变量输出 68 | * 69 | * @param mixed $arr 变量 70 | * @param boolean $echo 是否输出 默认为True 如果为false 则返回输出字符串 71 | * @param string $label 标签 默认为空 72 | * @param boolean $strict 是否严谨 默认为true 73 | * 74 | * @return void|string 75 | */ 76 | public static function dump($arr, $echo = true, $label = null, $strict = true) 77 | { 78 | ArrayHelper::dump($arr, $echo, $label, $strict); 79 | } 80 | 81 | /** 82 | * 扩展[[Yii::$app->getCache()->getOrSet()]]该方法,当`$duration`为`false`时先删除缓存再缓存执行数据结果 83 | * 84 | * @param mixed $key a key identifying the value to be cached. This can be a simple string or 85 | * a complex data structure consisting of factors representing the key. 86 | * @param callable|Closure $callable the callable or closure that will be used to generate a value to be cached. 87 | * In case $callable returns `false`, the value will not be cached. 88 | * @param int $duration default duration in seconds before the cache will expire. If not set, 89 | * [[defaultDuration]] value will be used. 90 | * @param Dependency|null $dependency dependency of the cached item. If the dependency changes, 91 | * the corresponding value in the cache will be invalidated when it is fetched via [[get()]]. 92 | * This parameter is ignored if [[serializer]] is `false`. 93 | * @param string $cache cache component 94 | * 95 | * @see \yii\caching\Cache::getOrSet() 96 | * @return mixed result of $callable execution 97 | */ 98 | public static function getOrSet($key, $callable, $duration = null, $dependency = null, $cache = 'cache') 99 | { 100 | /** @var \yii\caching\Cache $component */ 101 | $component = Yii::$app->get($cache); 102 | if ($duration === false) { 103 | $component->delete($key); 104 | } 105 | 106 | return $component->getOrSet($key, $callable, $duration, $dependency); 107 | } 108 | 109 | /** 110 | * 支持抛出模型类(Model|ActiveRecord)验证错误的事务操作 111 | * 112 | * 事务操作默认只抛出异常错误,如果需要抛出模型类产生的验证错误,`$callback`函数内需要被获取到的模型类必须使用 113 | * [[traits\ExtendModelTrait()]]用以支持该方法 114 | * 115 | * @param callable $callback a valid PHP callback that performs the job. Accepts connection instance as parameter. 116 | * @param string|null $isolationLevel The isolation level to use for this transaction. 117 | * 118 | * @throws \Exception 119 | * @return mixed result of callback function 120 | */ 121 | public static function transaction(callable $callback, $isolationLevel = null) 122 | { 123 | self::setThrowException(); 124 | $result = Yii::$app->getDb()->transaction($callback, $isolationLevel); 125 | self::setThrowException(false); 126 | 127 | return $result; 128 | } 129 | 130 | /** 131 | * 抛出异常,默认不抛出 132 | * 133 | * @var boolean 134 | */ 135 | protected static $_throwException = false; 136 | 137 | /** 138 | * 获取是否允许抛出异常 139 | * 140 | * @return boolean 141 | */ 142 | public static function getThrowException() 143 | { 144 | return static::$_throwException; 145 | } 146 | 147 | /** 148 | * 设置是否允许抛出异常,默认为`true`(允许) 149 | * 150 | * @param boolean $throw 151 | */ 152 | public static function setThrowException($throw = true) 153 | { 154 | static::$_throwException = $throw; 155 | } 156 | 157 | /** 158 | * 公共缓存类,主要是缓存一些公用的数据 159 | * 160 | * @return \yii\caching\Cache|object|null 161 | * @throws \yii\base\InvalidConfigException 162 | */ 163 | public static function cache() 164 | { 165 | return Yii::$app->get('commonCache'); 166 | } 167 | 168 | /** 169 | * 获取调度管理器 170 | * 171 | * @param Controller|DispatchTrait $controller 172 | * @param array $defaultDispatches 173 | * 174 | * @return object|DispatchManagerInterface 175 | */ 176 | public static function getDispatchManager($controller, array $defaultDispatches) 177 | { 178 | return Yii::$container->get( 179 | Yii::$container->has('wocenter\interfaces\DispatchManagerInterface') ? 180 | 'wocenter\interfaces\DispatchManagerInterface' : 181 | 'wocenter\core\DispatchManager', 182 | [ 183 | $controller, 184 | $defaultDispatches, 185 | ] 186 | ); 187 | } 188 | 189 | /** 190 | * 当前控制器所属的扩展信息 191 | * 192 | * @param Controller|DispatchTrait|ViewContextInterface $controller 193 | * 194 | * @return object|RunningExtensionInterface 195 | */ 196 | public static function getRunningExtension(Controller $controller) 197 | { 198 | if (Yii::$container->has(RunningExtensionInterface::class)) { 199 | $runningExtensionClass = RunningExtensionInterface::class; 200 | } else { 201 | $runningExtensionClass = WoCenterExtension::class; 202 | } 203 | 204 | return Yii::$container->get($runningExtensionClass, [$controller]); 205 | } 206 | 207 | /** 208 | * 获取当前主题的参数配置 209 | * 210 | * @param Controller $controller 211 | * 212 | * @see \wocenter\core\Theme::getThemeConfig() 213 | * @return array 214 | */ 215 | public static function getThemeConfig(Controller $controller): array 216 | { 217 | /** @var Theme $theme */ 218 | $theme = $controller->getView()->theme instanceof Theme ? 219 | $controller->getView()->theme : 220 | Yii::createObject(Theme::class); 221 | 222 | return $theme->getThemeConfig(); 223 | } 224 | 225 | } 226 | -------------------------------------------------------------------------------- /core/ConfigProvider.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class ConfigProvider extends BaseObject implements ConfigProviderInterface 15 | { 16 | 17 | /** 18 | * 配置数据 19 | * 20 | * @var array 21 | */ 22 | public $config = []; 23 | 24 | /** 25 | * 默认配置数据 26 | * 27 | * @var array 28 | */ 29 | protected $_defaultConfig = [ 30 | self::WEB_SITE_TITLE => [ 31 | 'name' => self::WEB_SITE_TITLE, 32 | 'title' => '网站名称', 33 | 'remark' => '网站名称', 34 | 'value' => 'WC后台管理系统', 35 | 'extra' => '', 36 | ], 37 | self::WEB_SITE_DESCRIPTION => [ 38 | 'name' => self::WEB_SITE_DESCRIPTION, 39 | 'title' => '网站简介', 40 | 'remark' => '搜索引擎描述', 41 | 'value' => '', 42 | 'extra' => '', 43 | ], 44 | self::WEB_SITE_KEYWORD => [ 45 | 'name' => self::WEB_SITE_KEYWORD, 46 | 'title' => '网站关键词', 47 | 'remark' => '搜索引擎关键词', 48 | 'value' => '', 49 | 'extra' => '', 50 | ], 51 | self::WEB_SITE_ICP => [ 52 | 'name' => self::WEB_SITE_ICP, 53 | 'title' => '网站备案号', 54 | 'remark' => '网站备案号,如:沪ICP备12345678号-9', 55 | 'value' => '', 56 | 'extra' => '', 57 | ], 58 | self::WEB_SITE_CLOSE => [ 59 | 'name' => self::WEB_SITE_CLOSE, 60 | 'title' => '关闭站点', 61 | 'remark' => '站点关闭后其他用户不能访问,管理员可以正常访问', 62 | 'value' => 1, 63 | 'extra' => '0:关闭,1:开启', 64 | ], 65 | self::WEB_SITE_CLOSE_TIPS => [ 66 | 'name' => self::WEB_SITE_CLOSE_TIPS, 67 | 'title' => '站点关闭提示语', 68 | 'remark' => '站点关闭后显示的提示信息', 69 | 'value' => '网站正在更新维护,请稍候再试~', 70 | 'extra' => '', 71 | ], 72 | ]; 73 | 74 | /** 75 | * @inheritdoc 76 | */ 77 | public function __construct(array $config = []) 78 | { 79 | $config['config'] = ArrayHelper::merge($this->_defaultConfig, $config['config'] ?? []); 80 | parent::__construct($config); 81 | } 82 | 83 | /** 84 | * @inheritdoc 85 | */ 86 | public function getAll() 87 | { 88 | return $this->config; 89 | } 90 | 91 | /** 92 | * @inheritdoc 93 | */ 94 | public function clearCache() 95 | { 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /core/ControllerInfo.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class ControllerInfo extends ExtensionInfo implements ControllerInfoInterface 15 | { 16 | 17 | use ExtensionTrait; 18 | 19 | /** 20 | * @var string 扩展所属模块ID 21 | */ 22 | protected $_moduleId; 23 | 24 | /** 25 | * @var string 数据库迁移路径 26 | */ 27 | private $_migrationPath; 28 | 29 | /** 30 | * @inheritdoc 31 | */ 32 | public function init() 33 | { 34 | parent::init(); 35 | // 如果没有指定扩展所属模块,则默认为扩展所属的应用 36 | if (empty($this->getModuleId())) { 37 | $this->setModuleId($this->app); 38 | } 39 | } 40 | 41 | /** 42 | * @inheritdoc 43 | */ 44 | public function getMenus() 45 | { 46 | return []; 47 | } 48 | 49 | /** 50 | * @inheritdoc 51 | */ 52 | public function getModuleId() 53 | { 54 | return $this->_moduleId; 55 | } 56 | 57 | /** 58 | * @inheritdoc 59 | */ 60 | public function setModuleId($_moduleId) 61 | { 62 | $this->_moduleId = $_moduleId; 63 | } 64 | 65 | /** 66 | * @inheritdoc 67 | */ 68 | public function getMigrationPath() 69 | { 70 | return $this->_migrationPath; 71 | } 72 | 73 | /** 74 | * @inheritdoc 75 | */ 76 | public function setMigrationPath($migrationPath) 77 | { 78 | $this->_migrationPath = $migrationPath; 79 | } 80 | 81 | /** 82 | * @var array 模块配置信息允许的键名 83 | */ 84 | private $_configKey = ['components', 'params']; 85 | 86 | /** 87 | * @inheritdoc 88 | */ 89 | public function getConfigKey() 90 | { 91 | return $this->_configKey; 92 | } 93 | 94 | /** 95 | * @inheritdoc 96 | */ 97 | public function install() 98 | { 99 | if (false == parent::install()) { 100 | return false; 101 | } 102 | $this->runMigrate('up'); 103 | 104 | return true; 105 | } 106 | 107 | /** 108 | * @inheritdoc 109 | */ 110 | public function uninstall() 111 | { 112 | if (false == parent::uninstall()) { 113 | return false; 114 | } 115 | $this->runMigrate('down'); 116 | 117 | return true; 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /core/DbConfigProvider.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class DbConfigProvider extends BaseObject implements ConfigProviderInterface 17 | { 18 | 19 | /** 20 | * @var Connection|string|array 数据库组件配置 21 | */ 22 | public $db = 'db'; 23 | 24 | /** 25 | * @var string 配置数据库表名 26 | */ 27 | public $tableName; 28 | 29 | /** 30 | * @var string 配置键名 31 | */ 32 | public $nameField = 'name'; 33 | 34 | /** 35 | * @var string 配置值字段 36 | */ 37 | public $valueField = 'value'; 38 | 39 | /** 40 | * @var string 额外数据字段 41 | */ 42 | public $extraField = 'extra'; 43 | 44 | /** 45 | * @inheritdoc 46 | */ 47 | public function init() 48 | { 49 | $this->db = Instance::ensure($this->db, Connection::class); 50 | } 51 | 52 | /** 53 | * @inheritdoc 54 | */ 55 | public function getAll() 56 | { 57 | return (new Query()) 58 | ->select([$this->nameField, $this->valueField, $this->extraField]) 59 | ->from($this->tableName) 60 | ->indexBy($this->nameField) 61 | ->all($this->db); 62 | } 63 | 64 | /** 65 | * @inheritdoc 66 | */ 67 | public function clearCache() 68 | { 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /core/Dispatch.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class Dispatch extends Action 18 | { 19 | 20 | /** 21 | * @var Controller|DispatchTrait 22 | */ 23 | public $controller; 24 | 25 | /** 26 | * @inheritdoc 27 | */ 28 | public function runWithParams($params) 29 | { 30 | if (!method_exists($this, 'run')) { 31 | throw new InvalidConfigException(get_class($this) . ' must define a "run()" method.'); 32 | } 33 | $args = $this->controller->bindActionParams($this, $params); 34 | Yii::debug('Running dispatch: ' . get_class($this) . '::run(), invoked by ' . get_class($this->controller), __METHOD__); 35 | if (Yii::$app->requestedParams === null) { 36 | Yii::$app->requestedParams = $args; 37 | } 38 | if ($this->beforeRun()) { 39 | $result = call_user_func_array([$this, 'run'], $args); 40 | $this->afterRun(); 41 | 42 | return $result; 43 | } else { 44 | return null; 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /core/ExtensionInfo.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class ExtensionInfo extends BaseObject implements ExtensionInfoInterface 17 | { 18 | 19 | /** 20 | * @var string 扩展唯一ID,不可重复 21 | */ 22 | private $_uniqueId; 23 | 24 | /** 25 | * @var string 扩展唯一名称,不可重复 26 | */ 27 | private $_uniqueName; 28 | 29 | /** 30 | * @var string 版本 31 | */ 32 | private $_version; 33 | 34 | /** 35 | * @var string 所属应用 36 | */ 37 | public $app; 38 | 39 | /** 40 | * @var string 扩展ID 41 | */ 42 | public $id; 43 | 44 | /** 45 | * @var string 名称 46 | */ 47 | public $name; 48 | 49 | /** 50 | * @var string 描述 51 | */ 52 | public $description; 53 | 54 | /** 55 | * @var string 扩展备注信息 56 | */ 57 | public $remark; 58 | 59 | /** 60 | * @var string 扩展网址 61 | */ 62 | public $url; 63 | 64 | /** 65 | * @var string 扩展的仓库网址,例如:https://github.com/Wonail/wocenter 66 | */ 67 | public $repositoryUrl; 68 | 69 | /** 70 | * @var string|array 开发者 71 | */ 72 | public $developer = 'WoCenter'; 73 | 74 | /** 75 | * @var string 开发者网站 76 | */ 77 | public $webSite; 78 | 79 | /** 80 | * @var string 开发者邮箱 81 | */ 82 | public $email; 83 | 84 | /** 85 | * @var boolean 是否系统扩展 86 | */ 87 | public $isSystem = false; 88 | 89 | /** 90 | * @var boolean 是否可安装 91 | */ 92 | public $canInstall = false; 93 | 94 | /** 95 | * @var boolean 是否可卸载 96 | */ 97 | public $canUninstall = false; 98 | 99 | /** 100 | * @var array 必须设置的属性值 101 | */ 102 | protected $mustBeSetProps = ['app', 'id']; 103 | 104 | /** 105 | * @var array 扩展所需依赖 106 | * [ 107 | * 'wocenter/yii2-module-system' => 'dev-master', 108 | * ] 109 | */ 110 | protected $depends = []; 111 | 112 | /** 113 | * @var array 扩展所需的composer包 114 | * [ 115 | * 'wonail/wocenter' => '~0.3', 116 | * ] 117 | */ 118 | protected $requirePackages = []; 119 | 120 | /** 121 | * @var string|null 获取扩展所属类型 122 | */ 123 | protected $category; 124 | 125 | /** 126 | * @inheritdoc 127 | */ 128 | public function __construct($uniqueId, $uniqueName, $version, array $config = []) 129 | { 130 | $this->_uniqueId = $uniqueId; 131 | $this->_uniqueName = $uniqueName; 132 | $this->_version = $version; 133 | parent::__construct($config); 134 | } 135 | 136 | /** 137 | * @inheritdoc 138 | */ 139 | public function init() 140 | { 141 | parent::init(); 142 | foreach ($this->mustBeSetProps as $prop) { 143 | if ($this->{$prop} === null) { 144 | throw new InvalidConfigException(get_called_class() . ': The `$' . $prop . '` property must be set.'); 145 | } 146 | } 147 | } 148 | 149 | /** 150 | * @inheritdoc 151 | */ 152 | public function install() 153 | { 154 | return true; 155 | } 156 | 157 | /** 158 | * @inheritdoc 159 | */ 160 | public function uninstall() 161 | { 162 | return true; 163 | } 164 | 165 | /** 166 | * @inheritdoc 167 | */ 168 | public function upgrade() 169 | { 170 | return true; 171 | } 172 | 173 | /** 174 | * @inheritdoc 175 | */ 176 | public function getUniqueId() 177 | { 178 | return $this->_uniqueId; 179 | } 180 | 181 | /** 182 | * @inheritdoc 183 | */ 184 | public function getUniqueName() 185 | { 186 | return $this->_uniqueName; 187 | } 188 | 189 | /** 190 | * @inheritdoc 191 | */ 192 | public function getVersion() 193 | { 194 | return $this->_version; 195 | } 196 | 197 | /** 198 | * @inheritdoc 199 | */ 200 | public function getDepends() 201 | { 202 | return $this->depends; 203 | } 204 | 205 | /** 206 | * @inheritdoc 207 | */ 208 | public function getRequirePackages() 209 | { 210 | return $this->requirePackages; 211 | } 212 | 213 | /** 214 | * @inheritdoc 215 | */ 216 | public function getCategory() 217 | { 218 | return $this->category; 219 | } 220 | 221 | /** 222 | * @inheritdoc 223 | */ 224 | public function getConfigKey() 225 | { 226 | return ['components', 'params']; 227 | } 228 | 229 | /** 230 | * @inheritdoc 231 | */ 232 | public function getConfig() 233 | { 234 | return []; 235 | } 236 | 237 | /** 238 | * @inheritdoc 239 | */ 240 | public function getCommonConfigKey() 241 | { 242 | return ['components', 'params']; 243 | } 244 | 245 | /** 246 | * @inheritdoc 247 | */ 248 | public function getCommonConfig() 249 | { 250 | return []; 251 | } 252 | 253 | /** 254 | * Returns url to repository for creation of new issue. 255 | * 256 | * @param string $path 257 | * 258 | * @return string 259 | */ 260 | final public function getIssueUrl($path = '/issues/new') 261 | { 262 | return self::getRepoUrl() ? (self::getRepoUrl() . $path) : ''; 263 | } 264 | 265 | /** 266 | * Returns url of official repository. 267 | * 268 | * @return string 269 | */ 270 | final public function getRepoUrl() 271 | { 272 | return $this->repositoryUrl; 273 | } 274 | 275 | } 276 | -------------------------------------------------------------------------------- /core/MenuProvider.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class MenuProvider extends BaseObject implements MenuProviderInterface 19 | { 20 | 21 | /** 22 | * @var string 系统扩展模块ID 23 | */ 24 | public $extensionId = 'extension'; 25 | 26 | /** 27 | * @var ActiveRecord 28 | */ 29 | private $_model; 30 | 31 | /** 32 | * 菜单配置数据 33 | * 34 | * @var array 35 | */ 36 | private $_menuConfig; 37 | 38 | private $_all; 39 | 40 | private $_allGroupByLevel; 41 | 42 | /** 43 | * @inheritdoc 44 | */ 45 | public function getAll($level = null): array 46 | { 47 | if (null !== $level) { 48 | if (null === $this->_allGroupByLevel) { 49 | $this->_allGroupByLevel = ArrayHelper::index( 50 | $this->_getAll(), 51 | 'id', 52 | 'level' 53 | ); 54 | } 55 | 56 | return $this->_allGroupByLevel[$level] ?? []; 57 | } 58 | 59 | return $this->_getAll(); 60 | } 61 | 62 | /** 63 | * 获取所有菜单数据 64 | * 65 | * @return array 66 | */ 67 | protected function _getAll() 68 | { 69 | if (null === $this->_all) { 70 | if (empty($this->getMenuConfig())) { 71 | return $this->_all = []; 72 | } 73 | $id = 0; 74 | foreach ($this->getMenuConfig() as $app => $row) { 75 | $this->_all = array_merge($this->_all ?? [], $this->_formatMenuConfig($row, $id, 0)); 76 | } 77 | $this->_all = ArrayHelper::index($this->_all, 'id'); 78 | } 79 | 80 | return $this->_all; 81 | } 82 | 83 | /** 84 | * @inheritdoc 85 | */ 86 | public function clearCache() 87 | { 88 | $this->_all = $this->_allGroupByLevel = null; 89 | } 90 | 91 | /** 92 | * @inheritdoc 93 | */ 94 | public function getModel() 95 | { 96 | if (null === $this->_model) { 97 | $this->setModel(); 98 | } 99 | 100 | return $this->_model; 101 | } 102 | 103 | /** 104 | * @inheritdoc 105 | */ 106 | public function setModel($config = []) 107 | { 108 | if (!isset($config['class'])) { 109 | throw new InvalidConfigException('The `$model` property must contain the `class` key name.'); 110 | } 111 | $this->_model = Yii::createObject($config); 112 | } 113 | 114 | /** 115 | * @inheritdoc 116 | */ 117 | public function getMenuConfig() 118 | { 119 | if (null === $this->_menuConfig) { 120 | $this->setMenuConfig(); 121 | } 122 | 123 | return $this->_menuConfig; 124 | } 125 | 126 | /** 127 | * @inheritdoc 128 | */ 129 | public function setMenuConfig($config = []) 130 | { 131 | foreach ($config ?: $this->defaultMenuConfig() as $app => $row) { 132 | $level = 1; 133 | $this->_menuConfig[$app] = $this->_initMenu($row, $app, $level); 134 | } 135 | } 136 | 137 | /** 138 | * 默认菜单配置,注意字段类型 139 | * 140 | * 菜单配置数组里,可用的键名包含以下: 141 | * - `label` string: 菜单名称,建议简写,如‘列表’、‘更新’、‘删除’等,通常在某些地方需要显示很多菜单项时使用, 142 | * 如系统的权限配置里。 143 | * - `alias_name` string: 菜单别名,建议完整显示,如‘用户列表’、‘更新用户’等。 144 | * - `icon_html` string: 菜单图标 145 | * - `modularity` string: 菜单所属的模块,一般为当前模块,不填写系统则会自动从`url`里提取出菜单所属的模块。 146 | * 如果`url`为空或不是规则的路由地址,如系统默认为父级菜单赋值的`#`情况时,必须明确该菜单所属模块, 147 | * 否则系统不会同步更新到菜单数据库里。 148 | * - `url` string: url地址,可以是系统已经配置了的路由映射地址,默认为`#` 149 | * - `params` array: url地址参数 150 | * - `menu_config` array: 菜单配置数据,一般用于小部件时,为小部件提供菜单本身所需的配置参数 151 | * - `show_on_menu` boolean: 是否显示该菜单,默认不显示 152 | * - `description` string: 菜单描述 153 | * - `sort_order` int: 排序 154 | * - `items` array: 子类菜单配置数组 155 | * - `theme` string: 菜单所属的主题,默认为'common',该值为系统保留字段,建议开发者避免使用该值作为主题名 156 | * 157 | * @return array 158 | */ 159 | protected function defaultMenuConfig(): array 160 | { 161 | return [ 162 | 'backend' => [ 163 | [ 164 | 'label' => '扩展中心', 165 | 'icon_html' => 'cube', 166 | 'modularity' => $this->extensionId, 167 | 'show_on_menu' => true, 168 | 'sort_order' => 1005, 169 | 'items' => [ 170 | // 扩展管理 171 | [ 172 | 'label' => '扩展管理', 173 | 'icon_html' => 'cubes', 174 | 'modularity' => $this->extensionId, 175 | 'show_on_menu' => true, 176 | 'items' => [ 177 | [ 178 | 'label' => '模块管理', 179 | 'url' => "/{$this->extensionId}/module/index", 180 | 'description' => '模块管理', 181 | 'show_on_menu' => true, 182 | 'items' => [ 183 | [ 184 | 'label' => '安装', 185 | 'alias_name' => '安装模块扩展', 186 | 'url' => "/{$this->extensionId}/module/install", 187 | ], 188 | [ 189 | 'label' => '管理', 190 | 'alias_name' => '管理模块扩展', 191 | 'url' => "/{$this->extensionId}/module/update", 192 | ], 193 | ], 194 | ], 195 | [ 196 | 'label' => '控制器管理', 197 | 'url' => "/{$this->extensionId}/controller/index", 198 | 'description' => '控制器管理', 199 | 'show_on_menu' => true, 200 | 'items' => [ 201 | [ 202 | 'label' => '安装', 203 | 'alias_name' => '安装控制器扩展', 204 | 'url' => "/{$this->extensionId}/controller/install", 205 | ], 206 | [ 207 | 'label' => '管理', 208 | 'alias_name' => '管理控制器扩展', 209 | 'url' => "/{$this->extensionId}/controller/update", 210 | ], 211 | ], 212 | ], 213 | [ 214 | 'label' => '主题管理', 215 | 'url' => "/{$this->extensionId}/theme/index", 216 | 'description' => '主题管理', 217 | 'show_on_menu' => true, 218 | 'items' => [ 219 | [ 220 | 'label' => '安装', 221 | 'alias_name' => '安装主题扩展', 222 | 'url' => "/{$this->extensionId}/theme/install", 223 | ], 224 | [ 225 | 'label' => '管理', 226 | 'alias_name' => '管理主题扩展', 227 | 'url' => "/{$this->extensionId}/theme/update", 228 | ], 229 | ], 230 | ], 231 | ], 232 | ], 233 | [ 234 | 'label' => '扩展功能', 235 | 'icon_html' => 'cogs', 236 | 'modularity' => $this->extensionId, 237 | 'show_on_menu' => true, 238 | 'items' => [ 239 | [ 240 | 'label' => '同步', 241 | 'alias_name' => '同步菜单', 242 | 'url' => "/{$this->extensionId}/functions/sync-menu", 243 | 'description' => '同步扩展菜单', 244 | 'show_on_menu' => true, 245 | 'params' => [ 246 | 'name' => 'value', 247 | ], 248 | 'menu_config' => [ 249 | 'linkOptions' => [ 250 | 'data-method' => 'post', 251 | 'data-pjax' => 1, 252 | ], 253 | ], 254 | ], 255 | [ 256 | 'label' => '清理', 257 | 'alias_name' => '清理缓存', 258 | 'url' => "/{$this->extensionId}/functions/clear-cache", 259 | 'description' => '清理扩展缓存', 260 | 'show_on_menu' => true, 261 | 'menu_config' => [ 262 | 'linkOptions' => [ 263 | 'data-method' => 'post', 264 | 'data-pjax' => 1, 265 | ], 266 | ], 267 | ], 268 | ], 269 | ], 270 | ], 271 | ], 272 | ], 273 | 'frontend' => [], 274 | ]; 275 | } 276 | 277 | /** 278 | * 格式化菜单配置数据为一元数组,并为菜单配置数据虚构`$id`和`$parent_id`值 279 | * 280 | * @param array $items 281 | * @param int $id 282 | * @param int $parent_id 283 | * 284 | * @return array 285 | */ 286 | private function _formatMenuConfig($items, &$id, $parent_id) 287 | { 288 | foreach ($items as $key => &$item) { 289 | $item['id'] = ++$id; 290 | $item['parent_id'] = $parent_id; 291 | if (isset($item['items'])) { 292 | $item['items'] = $this->_formatMenuConfig($item['items'], $id, $id); 293 | } 294 | } 295 | 296 | return ArrayHelper::treeToList($items); 297 | } 298 | 299 | /** 300 | * 初始化菜单 301 | * 302 | * @param array $items 303 | * @param string $app 304 | * @param int $level 305 | * 306 | * @return array 307 | */ 308 | private function _initMenu($items, $app, $level) 309 | { 310 | foreach ($items as $key => $item) { 311 | $this->initMenuData($item, $app, $level); 312 | if (isset($item['items'])) { 313 | // 当父类指定某个主题后,则子类也归属该主题 314 | $item['items'] = $this->_initMenu($item['items'], $app, $level + 1); 315 | } 316 | // 转换菜单数据键值,便于使用\yii\helpers\ArrayHelper::merge合并相同键名的数组到同一分组下 317 | $uniqueKey = $item['theme'] . '/' . $item['label']; 318 | $items[$uniqueKey] = ArrayHelper::merge($items[$uniqueKey] ?? [], $item); 319 | unset($items[$key]); 320 | } 321 | 322 | return $items; 323 | } 324 | 325 | /** 326 | * 初始化菜单配置数据 327 | * 补全修正菜单数组,确保可用字段存在于菜单数据表里 328 | * 329 | * @param array $item 330 | * @param string $app 331 | * @param int $level 332 | * 333 | * @throws InvalidConfigException 334 | */ 335 | protected function initMenuData(&$item = [], $app, $level) 336 | { 337 | if (!isset($item['label'])) { 338 | throw new InvalidConfigException("The 'label' option is required."); 339 | } 340 | foreach (['params', 'menu_config'] as $field) { 341 | $item[$field] = $item[$field] ?? []; 342 | if (!is_array($item[$field])) { 343 | throw new InvalidConfigException("Unsupported type for \"$field\": " . gettype($item[$field]) . 344 | "\n" . VarDumper::dumpAsString($item)); 345 | } 346 | } 347 | $item['alias_name'] = $item['alias_name'] ?? $item['label']; 348 | $item['category_id'] = $app; // 为菜单项添加分类ID 349 | $item['url'] = $item['url'] ?? '#'; 350 | // 模块ID 351 | if (!isset($item['modularity']) && $item['url'] != '#') { 352 | preg_match('/\w+/', $item['url'], $modularity); 353 | $item['modularity'] = $modularity[0]; 354 | } 355 | $item['created_type'] = $item['created_type'] ?? MenuProviderInterface::CREATE_TYPE_BY_EXTENSION; 356 | $item['show_on_menu'] = isset($item['show_on_menu']) ? intval($item['show_on_menu']) : 0; 357 | $item['sort_order'] = $item['sort_order'] ?? 0; 358 | $item['status'] = isset($item['status']) ? intval($item['status']) : 1; 359 | $item['theme'] = $item['theme'] ?? 'common'; 360 | // 需要补全空字符串的字段 361 | $fields = ['icon_html', 'description']; 362 | foreach ($fields as $field) { 363 | if (!isset($item[$field])) { 364 | $item[$field] = ''; 365 | } 366 | } 367 | $item['level'] = $level; // 虚拟菜单层级数据,用于定位菜单 368 | } 369 | 370 | } 371 | -------------------------------------------------------------------------------- /core/Model.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Model extends baseModel 14 | { 15 | 16 | use ExtendModelTrait; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /core/Modularity.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Modularity extends Module 13 | { 14 | 15 | /** 16 | * 更改默认路由是为了防止在系统使用调度服务时命名空间不支持`default`|`public`等字符时的问题 17 | * 18 | * @inheritdoc 19 | */ 20 | public $defaultRoute = 'common'; 21 | 22 | /** 23 | * @var string 基础命名空间,一般为当前模块的命名空间。 24 | */ 25 | public $baseNamespace; 26 | 27 | /** 28 | * @inheritdoc 29 | */ 30 | public function init() 31 | { 32 | if ($this->baseNamespace === null) { 33 | $class = get_class($this); 34 | if (($pos = strrpos($class, '\\')) !== false) { 35 | $this->baseNamespace = substr($class, 0, $pos); 36 | } 37 | } 38 | if ($this->controllerNamespace === null) { 39 | $this->controllerNamespace = $this->baseNamespace . '\\controllers'; 40 | } 41 | parent::init(); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /core/ModularityInfo.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class ModularityInfo extends ExtensionInfo implements ModularityInfoInterface 14 | { 15 | 16 | use ExtensionTrait; 17 | 18 | /** 19 | * @var boolean 是否启用bootstrap 20 | */ 21 | public $bootstrap = false; 22 | 23 | /** 24 | * @var string 数据库迁移路径 25 | */ 26 | private $_migrationPath; 27 | 28 | /** 29 | * @var array 模块配置信息允许的键名 30 | */ 31 | private $_configKey = ['components', 'params', 'modules']; 32 | 33 | /** 34 | * @inheritdoc 35 | */ 36 | public function getMenus() 37 | { 38 | return []; 39 | } 40 | 41 | /** 42 | * @inheritdoc 43 | */ 44 | public function getMigrationPath() 45 | { 46 | return $this->_migrationPath; 47 | } 48 | 49 | /** 50 | * @inheritdoc 51 | */ 52 | public function setMigrationPath($migrationPath) 53 | { 54 | $this->_migrationPath = $migrationPath; 55 | } 56 | 57 | /** 58 | * @inheritdoc 59 | */ 60 | public function getConfigKey() 61 | { 62 | return $this->_configKey; 63 | } 64 | 65 | /** 66 | * @inheritdoc 67 | */ 68 | public function install() 69 | { 70 | if (parent::install() == false) { 71 | return false; 72 | } 73 | $this->runMigrate('up'); 74 | 75 | return true; 76 | } 77 | 78 | /** 79 | * @inheritdoc 80 | */ 81 | public function uninstall() 82 | { 83 | if (parent::uninstall() == false) { 84 | return false; 85 | } 86 | $this->runMigrate('down'); 87 | 88 | return true; 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /core/Service.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class Service extends BaseObject implements ServiceInterface 23 | { 24 | 25 | /** 26 | * @var string 服务ID 27 | */ 28 | protected $id; 29 | 30 | /** 31 | * @var Service|null 当前服务的父级服务,默认为null,即当前服务为顶级服务 32 | */ 33 | public $service; 34 | 35 | /** 36 | * @var boolean 是否禁用服务类功能,默认不禁用 37 | */ 38 | public $disabled = false; 39 | 40 | /** 41 | * @var Service[]|array 已经实例化的子服务单例 42 | */ 43 | private $_subService; 44 | 45 | /** 46 | * @var mixed 服务类相关信息 47 | */ 48 | protected $_info = ''; 49 | 50 | /** 51 | * @var mixed 服务类相关数据 52 | */ 53 | protected $_data = ''; 54 | 55 | /** 56 | * @var boolean 服务类执行状态结果,默认为false 57 | */ 58 | protected $_status = false; 59 | 60 | /** 61 | * @var array 必须设置的属性值 62 | */ 63 | protected $mustBeSetProps = []; 64 | 65 | /** 66 | * @inheritdoc 67 | */ 68 | public function init() 69 | { 70 | parent::init(); 71 | if (!in_array('id', $this->mustBeSetProps)) { 72 | array_push($this->mustBeSetProps, 'id'); 73 | } 74 | foreach ($this->mustBeSetProps as $prop) { 75 | if ($this->{$prop} === null) { 76 | throw new InvalidConfigException(get_called_class() . ': The `$' . $prop . '` property must be set.'); 77 | } 78 | } 79 | } 80 | 81 | /** 82 | * @inheritdoc 83 | */ 84 | public function getUniqueId() 85 | { 86 | return $this->service ? $this->service->getUniqueId() . '/' . $this->id : $this->id; 87 | } 88 | 89 | /** 90 | * @inheritdoc 91 | */ 92 | public function coreServices() 93 | { 94 | return []; 95 | } 96 | 97 | /** 98 | * @inheritdoc 99 | */ 100 | public function getSubService($serviceName) 101 | { 102 | if (null === $this->_subService) { 103 | $this->setSubService([]); 104 | } 105 | if (!isset($this->_subService[$serviceName])) { 106 | throw new InvalidConfigException("The Service:`{$this->getUniqueId()}` required sub service component `{$serviceName}` is not found."); 107 | } elseif ($this->_subService[$serviceName] instanceof Service) { 108 | return $this->_subService[$serviceName]; 109 | } else { 110 | $uniqueName = $this->getUniqueId() . '/' . $serviceName; 111 | $this->_subService[$serviceName] = Yii::createObject(array_merge($this->_subService[$serviceName], [ 112 | 'service' => $this, 113 | ])); 114 | if (!$this->_subService[$serviceName] instanceof Service) { 115 | throw new InvalidConfigException("The required sub service component `{$uniqueName}` must return an object extends `\\wocenter\\core\\Service`."); 116 | } 117 | 118 | Yii::debug('Loading sub service: ' . $uniqueName, __METHOD__); 119 | 120 | return $this->_subService[$serviceName]; 121 | } 122 | } 123 | 124 | /** 125 | * @inheritdoc 126 | */ 127 | public function setSubService($config) 128 | { 129 | $this->_subService = ArrayHelper::merge($this->coreServices(), $config ?? []); 130 | } 131 | 132 | /** 133 | * @inheritdoc 134 | */ 135 | public function getInfo() 136 | { 137 | return $this->_info; 138 | } 139 | 140 | /** 141 | * @inheritdoc 142 | */ 143 | public function getData() 144 | { 145 | return $this->_data; 146 | } 147 | 148 | /** 149 | * @inheritdoc 150 | */ 151 | public function getResult() 152 | { 153 | return [ 154 | 'status' => $this->_status, 155 | 'info' => $this->getInfo(), 156 | 'data' => $this->getData(), 157 | ]; 158 | } 159 | 160 | /** 161 | * @var integer|false 缓存时间间隔 162 | */ 163 | private $_cacheDuration; 164 | 165 | /** 166 | * @inheritdoc 167 | */ 168 | public function getCacheDuration() 169 | { 170 | if (null === $this->_cacheDuration) { 171 | if (null !== $this->service) { 172 | return $this->service->getCacheDuration(); 173 | } 174 | $this->setCacheDuration(); 175 | } 176 | 177 | return $this->_cacheDuration; 178 | } 179 | 180 | /** 181 | * @inheritdoc 182 | */ 183 | public function setCacheDuration($cacheDuration = 86400) 184 | { 185 | $this->_cacheDuration = $cacheDuration; 186 | 187 | return $this; 188 | } 189 | 190 | /** 191 | * @inheritdoc 192 | */ 193 | public function clearCache() 194 | { 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /core/ServiceLocator.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class ServiceLocator extends BaseObject 19 | { 20 | 21 | /** 22 | * @var Module 获取服务组件的容器 23 | */ 24 | public $container; 25 | 26 | /** 27 | * @throws InvalidConfigException 28 | */ 29 | public function init() 30 | { 31 | parent::init(); 32 | $this->container = $this->container ?: Yii::$app; 33 | if (!is_subclass_of($this->container, Module::class)) { 34 | throw new InvalidConfigException("The `\$container` property must return an object extends `\\yii\\base\\Module`."); 35 | } 36 | } 37 | 38 | /** 39 | * 获取系统顶级服务类 40 | * 41 | * @param string $serviceName 服务名,不带后缀`ExtensionService`,如:`passport`、`log` 42 | * 43 | * @return Service|object|null 44 | * @throws InvalidConfigException 45 | */ 46 | public function getService($serviceName) 47 | { 48 | $service = $serviceName . 'Service'; 49 | if (!$this->container->has($service, true)) { 50 | /** @var Service $component */ 51 | $component = $this->container->get($service); 52 | if (!$component instanceof Service) { 53 | throw new InvalidConfigException("The required service component `{$service}` must return 54 | an object extends `\\wocenter\\core\\ExtensionService`."); 55 | } 56 | 57 | if (YII_ENV_DEV) { 58 | Yii::debug("Loading service: {$service}: {$component->getUniqueId()}", __METHOD__); 59 | } 60 | 61 | return $component; 62 | } else { 63 | return $this->container->get($service); 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /core/Theme.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Theme extends \yii\base\Theme 16 | { 17 | 18 | public function __construct($config = []) 19 | { 20 | if (!isset($config['pathMap'])) { 21 | $this->pathMap = $this->getDefaultPathMap(); 22 | } 23 | parent::__construct($config); 24 | } 25 | 26 | /** 27 | * 获取当前主题的参数配置 28 | * - string 'name': 当前主题的ID 29 | * - string 'dispatch': 当前主题的公共调度器行为类 30 | * - string|array 'viewPath': 当前主题的视图目录地址,支持数组格式映射 31 | * @example 32 | * ```php 33 | * 'viewPath' => [ 34 | * '@app/themes/basic', 35 | * '@app/themes/adminlte', 36 | * ] 37 | * ``` 38 | * @see ThemeInfo::getConfig() 39 | * 40 | * @return array 41 | */ 42 | public function getThemeConfig(): array 43 | { 44 | return Yii::$app->params['themeConfig'] ?? [ 45 | 'name' => 'basic', 46 | 'dispatch' => '\wocenter\behaviors\DispatchBehavior', 47 | 'viewPath' => '@app/themes/basic', 48 | ]; 49 | } 50 | 51 | /** 52 | * 获取默认的视图路径映射 53 | * 54 | * @return array 55 | */ 56 | public function getDefaultPathMap(): array 57 | { 58 | $pathMap = [ 59 | '@developer' => [ 60 | '@developer', 61 | '@extensions', 62 | ], 63 | '@extensions' => [ 64 | '@developer', // 只要开发者目录内存在系统扩展里相应的视图文件,即可优先被渲染 65 | '@extensions', 66 | ], 67 | ]; 68 | // 添加对'@app/views'视图路径的映射 69 | foreach ((array) $this->getThemeConfig()['viewPath'] as $key => $value) { 70 | $pathMap['@app/views'][$key] = $value; 71 | } 72 | 73 | return $pathMap; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /core/ThemeInfo.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class ThemeInfo extends ExtensionInfo implements ThemeInfoInterface 15 | { 16 | 17 | /** 18 | * 主题公共调度器行为类 19 | * 20 | * @return string 21 | */ 22 | public $dispatch = '\wocenter\behaviors\DispatchBehavior'; 23 | 24 | /** 25 | * @var string 主题视图路径 26 | */ 27 | private $_viewPath; 28 | 29 | /** 30 | * 获取主题视图路径 31 | * 32 | * @return string 33 | */ 34 | public function getViewPath() 35 | { 36 | return $this->_viewPath; 37 | } 38 | 39 | /** 40 | * 设置主题视图路径 41 | * 42 | * @param string $viewPath 主题视图路径 43 | */ 44 | public function setViewPath($viewPath) 45 | { 46 | $this->_viewPath = $viewPath; 47 | } 48 | 49 | /** 50 | * @inheritdoc 51 | * 52 | * - 添加对扩展目录和开发者目录视图的映射 53 | * - 添加默认的主题参数配置 54 | * @see \wocenter\core\Theme::getThemeConfig() 55 | * 56 | */ 57 | public function getConfig() 58 | { 59 | return [ 60 | 'components' => [ 61 | 'view' => [ 62 | 'theme' => [ 63 | 'class' => '\wocenter\core\Theme', 64 | ], 65 | ], 66 | ], 67 | 'params' => [ 68 | // 添加当前主题配置参数 69 | 'themeConfig' => [ 70 | 'name' => $this->id, // 当前主题ID,如:adminlte、basic。该值决定调度器在哪个主题目录下获取调度器 71 | 'dispatch' => $this->dispatch, // 当前主题公共调度器 72 | 'viewPath' => $this->getViewPath(), // 当前主题的视图路径 73 | ], 74 | ], 75 | ]; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /core/WoCenterExtension.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class WoCenterExtension extends BaseObject implements RunningExtensionInterface 20 | { 21 | 22 | /** 23 | * 调用该类的控制器 24 | * 25 | * @var Controller|DispatchTrait 26 | */ 27 | protected $controller; 28 | 29 | /** 30 | * @var string 当前控制器的上级命名空间 31 | */ 32 | private $_namespace; 33 | 34 | /** 35 | * @inheritdoc 36 | */ 37 | public function __construct(Controller $controller, array $config = []) 38 | { 39 | $this->controller = $controller; 40 | parent::__construct($config); 41 | } 42 | 43 | /** 44 | * @inheritdoc 45 | */ 46 | public function init() 47 | { 48 | $this->_namespace = ($this->controller->module instanceof Modularity) ? 49 | $this->controller->module->baseNamespace : 50 | substr((new \ReflectionClass($this->controller))->getNamespaceName(), 0, -12); 51 | } 52 | 53 | /** 54 | * @inheritdoc 55 | */ 56 | public function getNamespace(): string 57 | { 58 | return $this->_namespace; 59 | } 60 | 61 | /** 62 | * @inheritdoc 63 | */ 64 | public function isExtensionController(): bool 65 | { 66 | return strpos($this->_namespace, 'extensions') === 0; 67 | } 68 | 69 | /** 70 | * @inheritdoc 71 | */ 72 | public function getInfo() 73 | { 74 | return $this->defaultExtension(); 75 | } 76 | 77 | /** 78 | * @inheritdoc 79 | */ 80 | public function getDbConfig(): array 81 | { 82 | return [ 83 | 'run' => ExtensionInfoInterface::RUN_MODULE_EXTENSION, 84 | ]; 85 | } 86 | 87 | /** 88 | * @inheritdoc 89 | */ 90 | public function getExtensionUniqueName(): string 91 | { 92 | return $this->getInfo()->getUniqueName(); 93 | } 94 | 95 | /** 96 | * @inheritdoc 97 | */ 98 | public function defaultExtension() 99 | { 100 | $info = Json::decode(file_get_contents(Yii::getAlias('@wocenter/composer.json'))); 101 | 102 | return Yii::createObject([ 103 | 'class' => ExtensionInfo::class, 104 | 'app' => 'Application', 105 | 'id' => 'wocenter', 106 | 'repositoryUrl' => $info['homepage'], 107 | 'name' => 'WoCenter核心', 108 | 'description' => $info['description'], 109 | 'developer' => $info['authors'], 110 | ], [ 111 | 'vendor/' . $info['name'], 112 | 'vendor/' . $info['name'], 113 | $info['version'] ?? Wc::getVersion(), 114 | ]); 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /core/web/Controller.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Controller extends baseController 14 | { 15 | 16 | use DispatchTrait; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /core/web/Dispatch.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class Dispatch extends baseDispatch 23 | { 24 | 25 | /** 26 | * @var Controller|DispatchTrait 27 | */ 28 | public $controller; 29 | 30 | public function behaviors() 31 | { 32 | return [ 33 | 'dispatch' => [ 34 | 'class' => 'wocenter\behaviors\DispatchBehavior', 35 | ], 36 | ]; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /db/ActiveRecord.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class ActiveRecord extends baseActiveRecord 14 | { 15 | 16 | use ExtendModelTrait; 17 | 18 | /** 19 | * @inheritdoc 20 | */ 21 | public function afterDelete() 22 | { 23 | parent::afterDelete(); 24 | $this->clearCache(); 25 | } 26 | 27 | /** 28 | * @inheritdoc 29 | */ 30 | public function afterSave($insert, $changedAttributes) 31 | { 32 | parent::afterSave($insert, $changedAttributes); 33 | $this->clearCache(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /db/Migration.php: -------------------------------------------------------------------------------- 1 | db->driverName === 'mysql') { 23 | $this->tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'; 24 | } 25 | } 26 | 27 | /** 28 | * 设置表注释 29 | * 30 | * @param string $comment 表注释 31 | * 32 | * @return string 33 | */ 34 | protected function buildTableComment($comment = '') 35 | { 36 | return $comment !== '' ? ' COMMENT = ' . $this->db->quoteValue($comment) : ''; 37 | } 38 | 39 | /** 40 | * 设置外键约束 41 | * 42 | * @param boolean $check 默认为`false`,取消约束 43 | */ 44 | protected function setForeignKeyCheck($check = false) 45 | { 46 | $this->execute('SET foreign_key_checks = ' . (int)$check . ';'); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /dispatches/BaseDelete.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class BaseDelete extends Dispatch 17 | { 18 | 19 | /** 20 | * @var string|ActiveRecord 需要操作的模型对象名, e.g. `User::class` 21 | */ 22 | public $modelClass = null; 23 | 24 | /** 25 | * @var boolean 是否标记删除而非真实删除,默认为标记删除 26 | */ 27 | public $markAsDeleted = true; 28 | 29 | /** 30 | * @var string 标记删除的操作字段 31 | */ 32 | public $deletedMarkAttribute = 'is_active'; 33 | 34 | /** 35 | * @var integer 标记删除的值 e.g. 0为删除,1为激活 36 | */ 37 | public $deletedMarkValue = 0; 38 | 39 | /** 40 | * @var string|array 操作成功后需要跳转的地址,默认跳转到来源地址 41 | */ 42 | public $successJumpUrl = null; 43 | 44 | /** 45 | * @var string|array 操作失败后需要跳转的地址,默认跳转到来源地址 46 | */ 47 | public $errorJumpUrl = null; 48 | 49 | /** 50 | * @var array|string url地址参数,仅在`$successJumpUrl`或`$errorJumpUrl`不为`null`时生效 51 | * - `string`: 自动从当前请求地址里提取参数,如:'name, name2'。如果请求地址里不存在指定参数,则抛出异常 52 | * - `array`: 自定义url地址参数,如:['name' => 'value', 'name2' => 'value2'] 53 | */ 54 | public $urlParams = []; 55 | 56 | /** 57 | * @inheritdoc 58 | * @throws BadRequestHttpException 59 | */ 60 | public function init() 61 | { 62 | if (!empty($this->urlParams) && is_string($this->urlParams)) { 63 | $params = []; 64 | foreach (StringHelper::stringToArray($this->urlParams) as $param) { 65 | $params[$param] = Yii::$app->getRequest()->getQueryParam($param); 66 | if ($params[$param] === null) { 67 | throw new BadRequestHttpException(Yii::t('yii', 'Missing required parameters: {params}', [ 68 | 'params' => $param, 69 | ])); 70 | } 71 | } 72 | $this->urlParams = $params; 73 | } 74 | } 75 | 76 | /** 77 | * @param bool $res 78 | * @param string $message 79 | * 80 | * @return \yii\web\Response 81 | */ 82 | protected function response($res, $message = '') 83 | { 84 | if ($res) { 85 | return $this->success($message ?: Yii::t('wocenter/app', 'Delete successful.'), 86 | $this->successJumpUrl ? array_merge([$this->successJumpUrl], $this->urlParams) : Yii::$app->getRequest()->getReferrer() 87 | ); 88 | } else { 89 | return $this->error($message ?: Yii::t('wocenter/app', 'Delete failure.'), 90 | $this->errorJumpUrl ? array_merge([$this->errorJumpUrl], $this->urlParams) : Yii::$app->getRequest()->getReferrer() 91 | ); 92 | } 93 | } 94 | 95 | } -------------------------------------------------------------------------------- /dispatches/DeleteOne.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class DeleteOne extends BaseDelete 15 | { 16 | 17 | use LoadModelTrait; 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | public function run($id) 23 | { 24 | /** @var ActiveRecord $model */ 25 | $model = $this->loadModel($this->modelClass, $id); 26 | $res = Wc::transaction(function () use ($model) { 27 | if ($this->markAsDeleted === true) { 28 | $model->setAttribute($this->deletedMarkAttribute, $this->deletedMarkValue); 29 | 30 | return $model->save(false); 31 | } else { 32 | return $model->delete(); 33 | } 34 | }); 35 | 36 | return $this->response($res, $model->_message); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /dispatches/MultipleDelete.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class MultipleDelete extends BaseDelete 16 | { 17 | 18 | /** 19 | * @inheritdoc 20 | */ 21 | public function init() 22 | { 23 | if (!isset($this->modelClass)) { 24 | throw new InvalidConfigException("The property `modelClass` should be set in controller `\$defaultDispatches`."); 25 | } 26 | if (!class_exists($this->modelClass)) { 27 | throw new InvalidConfigException("Model class `{$this->modelClass}` does not exists"); 28 | } 29 | parent::init(); 30 | } 31 | 32 | /** 33 | * @inheritdoc 34 | */ 35 | public function run($id) 36 | { 37 | $res = false; 38 | $message = ''; 39 | /** @var ActiveRecord[] $items */ 40 | $items = $this->modelClass::findAll(StringHelper::parseIds($id)); 41 | foreach ($items as $item) { 42 | $res = Wc::transaction(function () use ($item) { 43 | if ($this->markAsDeleted === true) { 44 | $item->setAttribute($this->deletedMarkAttribute, $this->deletedMarkValue); 45 | 46 | return $item->save(false); 47 | } else { 48 | return $item->delete(); 49 | } 50 | }); 51 | if (!$res) { 52 | $message = $item->_message; 53 | break; 54 | } 55 | } 56 | 57 | return $this->response($res, $message); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /enums/EnableEnum.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class EnableEnum extends Enums 13 | { 14 | 15 | const DISABLE = 0; 16 | const ENABLE = 1; 17 | 18 | /** 19 | * @inheritdoc 20 | */ 21 | public static function list() 22 | { 23 | return [ 24 | self::DISABLE => Yii::t('wocenter/app', 'Disable'), 25 | self::ENABLE => Yii::t('wocenter/app', 'Enable'), 26 | ]; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /enums/Enums.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | abstract class Enums implements EnumInterface 14 | { 15 | 16 | /** 17 | * 获取值 18 | * 19 | * @param string|\Closure|array $key 20 | * 21 | * @return mixed 22 | */ 23 | public static function value($key) 24 | { 25 | return ArrayHelper::getValue(static::list(), $key); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /enums/GenderEnum.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class GenderEnum extends Enums 13 | { 14 | 15 | const UNKNOWN = 0; 16 | const MALE = 1; 17 | const FEMALE = 2; 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | public static function list() 23 | { 24 | return [ 25 | self::UNKNOWN => Yii::t('wocenter/app', 'Secrecy'), 26 | self::MAN => Yii::t('wocenter/app', 'Male'), 27 | self::WOMAN => Yii::t('wocenter/app', 'Female'), 28 | ]; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /enums/TargetEnum.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class TargetEnum extends Enums 13 | { 14 | 15 | const TARGET_SELF = 0; 16 | const TARGET_BLANK = 1; 17 | 18 | /** 19 | * @inheritdoc 20 | */ 21 | public static function list() 22 | { 23 | return [ 24 | self::TARGET_SELF => Yii::t('wocenter/app', 'Target self'), 25 | self::TARGET_BLANK => Yii::t('wocenter/app', 'Target blank'), 26 | ]; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /enums/VisibleEnum.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class VisibleEnum extends Enums 13 | { 14 | 15 | const INVISIBLE = 0; 16 | const VISIBLE = 1; 17 | 18 | /** 19 | * @inheritdoc 20 | */ 21 | public static function list() 22 | { 23 | return [ 24 | self::INVISIBLE => Yii::t('wocenter/app', 'Hidden'), 25 | self::VISIBLE => Yii::t('wocenter/app', 'Display'), 26 | ]; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /enums/YesEnum.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class YesEnum extends Enums 13 | { 14 | 15 | const NO = 0; 16 | const YES = 1; 17 | 18 | /** 19 | * @inheritdoc 20 | */ 21 | public static function list() 22 | { 23 | return [ 24 | self::NO => Yii::t('wocenter/app', 'No'), 25 | self::YES => Yii::t('wocenter/app', 'Yes'), 26 | ]; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /filters/LogActionTime.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class LogActionTime extends ActionFilter 14 | { 15 | 16 | private $_startTime; 17 | 18 | public function beforeAction($action) 19 | { 20 | $this->_startTime = microtime(true); 21 | 22 | return parent::beforeAction($action); 23 | } 24 | 25 | public function afterAction($action, $result) 26 | { 27 | $time = microtime(true) - $this->_startTime; 28 | $time = number_format($time, 4); 29 | Yii::debug("Action '{$action->uniqueId}' spent $time second."); 30 | 31 | return parent::afterAction($action, $result); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /grid/DateTimeColumn.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class DateTimeColumn extends DataColumn 15 | { 16 | 17 | public $format = 'datetime'; 18 | 19 | public function getDataCellValue($model, $key, $index) 20 | { 21 | if ($this->value !== null) { 22 | if (is_string($this->value)) { 23 | // 不存在则显示返回null,主要是用于更正时间日期的显示,因为时间为0时同样会被格式化 24 | return ArrayHelper::getValue($model, $this->value) ?: null; 25 | } else { 26 | return call_user_func($this->value, $model, $key, $index, $this); 27 | } 28 | } elseif ($this->attribute !== null) { 29 | // 不存在则显示返回null,主要是用于更正时间日期的显示,因为时间为0时同样会被格式化 30 | return ArrayHelper::getValue($model, $this->attribute) ?: null; 31 | } 32 | 33 | return null; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /helpers/ArrayHelper.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class ArrayHelper extends baseArrayHelper 13 | { 14 | 15 | /** 16 | * 数组转换为字符串,主要用于把分隔符调整到第二个参数 17 | * 18 | * @param array $arr 要连接的数组 19 | * @param string $glue 分割符 20 | * 21 | * @return string 22 | */ 23 | public static function arrayToString($arr, $glue = ','): string 24 | { 25 | return implode($glue, $arr); 26 | } 27 | 28 | /** 29 | * 不区分大小写的in_array实现 30 | * 31 | * @param string $key 待查询的key 32 | * @param mixed $data 被查询的数据 33 | * @param boolean $validate_string 是否验证包含字符串,默认不验证 34 | * @param boolean $inverse 参数反转,仅在$validate_string为true时生效。 35 | * 当$inverse参数为true时,即在$key里查询$array里的值。 36 | * 例如:ArrayHelper::inArrayCase('administrators', ['admin', 'guest], true, true)结果为true 37 | * 38 | * @return boolean 39 | */ 40 | public static function inArrayCase($key, $data, $validate_string = false, $inverse = false): bool 41 | { 42 | if (!is_array($data)) { 43 | $data = explode(',', $data); 44 | } 45 | foreach ($data as $k) { 46 | if (strcasecmp($key, $k) === 0) { 47 | return true; 48 | } 49 | if ($validate_string) { 50 | return $inverse ? (strpos($k, strtolower($key)) !== false) : (strpos(strtolower($key), $k) !== false); 51 | } 52 | continue; 53 | } 54 | 55 | return false; 56 | } 57 | 58 | /** 59 | * 浏览器友好的变量输出 60 | * 61 | * @param mixed $var 变量 62 | * @param boolean $echo 是否输出,默认为`true`。如果为`false`,则返回输出字符串 63 | * @param string $label 标签,默认为空 64 | * @param boolean $strict 是否严谨,默认为`true` 65 | * 66 | * @return string|null 67 | */ 68 | public static function dump($var, $echo = true, $label = null, $strict = true) 69 | { 70 | $label = ($label === null) ? '' : rtrim($label) . ' '; 71 | if (!$strict) { 72 | if (ini_get('html_errors')) { 73 | $output = print_r($var, true); 74 | $output = '
' . $label . htmlspecialchars($output, ENT_QUOTES) . '
'; 75 | } else { 76 | $output = $label . print_r($var, true); 77 | } 78 | } else { 79 | ob_start(); 80 | var_dump($var); 81 | $output = ob_get_clean(); 82 | if (!extension_loaded('xdebug')) { 83 | $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output); 84 | $output = '
' . $label . htmlspecialchars($output, ENT_QUOTES) . '
'; 85 | } 86 | } 87 | if ($echo) { 88 | echo($output); 89 | 90 | return null; 91 | } else { 92 | return $output; 93 | } 94 | } 95 | 96 | /** 97 | * 把返回的数据集转换成Tree 98 | * 99 | * @param array $list 要转换的数据集 100 | * @param string $pk 供pid索引的id字段 101 | * @param string $pid parent标记字段 102 | * @param string $child 子类标记字段 103 | * @param integer $root 顶级菜单数值,默认为`0`,表示显示到最顶级菜单 104 | * 105 | * @return array 106 | */ 107 | public static function listToTree($list, $pk = 'id', $pid = 'parent_id', $child = 'items', $root = 0): array 108 | { 109 | if (empty($list)) { 110 | return []; 111 | } 112 | // 创建Tree 113 | $tree = []; 114 | if (is_array($list)) { 115 | // 创建基于主键的数组引用 116 | $refer = []; 117 | foreach ($list as $key => $data) { 118 | $refer[$data[$pk]] = &$list[$key]; 119 | } 120 | foreach ($list as $key => $data) { 121 | // 判断是否存在parent 122 | $parentId = $data[$pid]; 123 | if ($root == $parentId) { 124 | $tree[] = &$list[$key]; 125 | } else { 126 | if (isset($refer[$parentId])) { 127 | $parent = &$refer[$parentId]; 128 | $parent[$child][] = &$list[$key]; 129 | } 130 | } 131 | } 132 | } 133 | 134 | return $tree; 135 | } 136 | 137 | /** 138 | * 将listToTree的树还原成列表 139 | * 140 | * @param array $tree 原来的树 141 | * @param string $child 子类节点的键 142 | * @param string|null $order 排序显示的键,一般是主键 升序排列,默认为`null`,表示不排序 143 | * @param array $list 过渡用的中间数组, 144 | * 145 | * @return array 返回排过序的列表数组 146 | */ 147 | public static function treeToList($tree, $child = 'items', $order = null, &$list = []): array 148 | { 149 | if (is_array($tree)) { 150 | foreach ($tree as $key => $value) { 151 | $refer = $value; 152 | if (isset($refer[$child])) { 153 | unset($refer[$child]); 154 | self::treeToList($value[$child], $child, $order, $list); 155 | } 156 | $list[] = $refer; 157 | } 158 | if ($order !== null) { 159 | $list = self::listSortBy($list, $order, 'asc'); 160 | } 161 | } 162 | 163 | return $list; 164 | } 165 | 166 | /** 167 | * 在数组列表中搜索 168 | * 169 | * @param array $list 数据列表 170 | * @param mixed $condition 查询条件,支持数组、操作符 ['neq', 'eq', 'not in', 'in']、字符串、todo 正则 171 | * 1: ['name'=>$value] 172 | * 2: 'name=value&name1=value1' 173 | * 3: ['name' => ['not in' => ['value1', 'value2']]] 174 | * 175 | * @return array 176 | */ 177 | public static function listSearch($list = [], $condition = '') 178 | { 179 | if (empty($list) || empty($condition)) { 180 | return []; 181 | } 182 | if (is_string($condition)) { 183 | parse_str($condition, $condition); 184 | } 185 | 186 | // 返回的结果集合 187 | $resultSet = []; 188 | foreach ($list as $key => $data) { 189 | $find = false; 190 | foreach ($condition as $field => $value) { 191 | // 排除数组里不存在的搜索条件 192 | if (!isset($data[$field])) { 193 | continue; 194 | } 195 | if (is_array($value)) { 196 | switch ($value[0]) { 197 | // 不等于 198 | case 'neq': 199 | $find = $data[$field] !== $value[1]; 200 | break; 201 | // 等于 202 | case 'eq': 203 | $find = $data[$field] == $value[1]; 204 | break; 205 | // 不包含 206 | case 'not in': 207 | $find = !in_array($data[$field], $value[1]); 208 | break; 209 | // 包含 210 | case 'in': 211 | $find = in_array($data[$field], $value[1]); 212 | break; 213 | // 大于 214 | case 'gt': 215 | $find = $data[$field] > $value[1]; 216 | break; 217 | // 小于 218 | case 'lt': 219 | $find = $data[$field] < $value[1]; 220 | break; 221 | } 222 | } elseif ($data[$field] == $value) { 223 | $find = true; 224 | } else { 225 | $find = false; 226 | } 227 | // 多条件查询时,只要有一个条件不满足,则标记为false,并跳过此次循环 228 | // todo 是否根据第三个参数$filter来判断查询条件是OR或AND 229 | if ($find == false) { 230 | break; 231 | } 232 | } 233 | if ($find) { 234 | $resultSet[] = $list[$key]; 235 | } 236 | } 237 | 238 | return $resultSet; 239 | } 240 | 241 | /** 242 | * 对查询结果集进行排序 243 | * 244 | * @param array $list 查询结果 245 | * @param string $field 排序的字段名 246 | * @param string $sortBy 排序类型 247 | * asc正向排序 desc逆向排序 nat自然排序 248 | * 249 | * @return array 250 | */ 251 | public static function listSortBy($list, $field, $sortBy = 'asc'): array 252 | { 253 | if (is_array($list)) { 254 | $refer = $resultSet = []; 255 | 256 | foreach ($list as $i => $data) { 257 | $refer[$i] = &$data[$field]; 258 | } 259 | 260 | switch ($sortBy) { 261 | // 正向排序 262 | case 'asc': 263 | asort($refer); 264 | break; 265 | // 逆向排序 266 | case 'desc': 267 | arsort($refer); 268 | break; 269 | // 自然排序 270 | case 'nat': 271 | natcasesort($refer); 272 | break; 273 | } 274 | foreach ($refer as $key => $val) { 275 | $resultSet[] = &$list[$key]; 276 | } 277 | 278 | return $resultSet; 279 | } 280 | 281 | return []; 282 | } 283 | 284 | } 285 | -------------------------------------------------------------------------------- /helpers/CacheManagerHelper.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class CacheManagerHelper 16 | { 17 | 18 | /** 19 | * Flushes all caches registered in the system. 20 | */ 21 | public static function flushAll() 22 | { 23 | $cachesInfo = []; 24 | if (empty($caches = self::findCaches())) { 25 | return $cachesInfo; 26 | } 27 | 28 | foreach ($caches as $name => $class) { 29 | $cachesInfo[] = [ 30 | 'name' => $name, 31 | 'class' => $class, 32 | 'is_flushed' => self::_canBeFlushed($class) ? Yii::$app->get($name)->flush() : false, 33 | ]; 34 | } 35 | 36 | return $cachesInfo; 37 | } 38 | 39 | /** 40 | * Returns array of caches in the system, keys are cache components names, values are class names. 41 | * 42 | * @param array $cachesNames caches to be found 43 | * 44 | * @return array 45 | */ 46 | private static function findCaches(array $cachesNames = []) 47 | { 48 | $caches = []; 49 | $components = Yii::$app->getComponents(); 50 | $findAll = ($cachesNames === []); 51 | 52 | foreach ($components as $name => $component) { 53 | if (!$findAll && !in_array($name, $cachesNames, true)) { 54 | continue; 55 | } 56 | 57 | if ($component instanceof CacheInterface) { 58 | $caches[$name] = get_class($component); 59 | } elseif (is_array($component) && isset($component['class']) && self::_isCacheClass($component['class'])) { 60 | $caches[$name] = $component['class']; 61 | } elseif (is_string($component) && self::_isCacheClass($component)) { 62 | $caches[$name] = $component; 63 | } elseif ($component instanceof \Closure) { 64 | $cache = Yii::$app->get($name); 65 | if (self::_isCacheClass($cache)) { 66 | $caches[$name] = get_class($cache); 67 | } 68 | } 69 | } 70 | 71 | return $caches; 72 | } 73 | 74 | /** 75 | * Checks if given class is a Cache class. 76 | * 77 | * @param string $className class name. 78 | * 79 | * @return bool 80 | */ 81 | private static function _isCacheClass($className) 82 | { 83 | return is_subclass_of($className, 'yii\caching\CacheInterface'); 84 | } 85 | 86 | /** 87 | * Checks if cache of a certain class can be flushed. 88 | * 89 | * @param string $className class name. 90 | * 91 | * @return bool 92 | */ 93 | private static function _canBeFlushed($className) 94 | { 95 | return !is_a($className, ApcCache::class, true) || php_sapi_name() !== 'cli'; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /helpers/ConsoleHelper.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class ConsoleHelper extends BaseObject 16 | { 17 | 18 | /** 19 | * 是否 Windows 系统 20 | * 21 | * @return bool 22 | */ 23 | public static function isRunningOnWindows() 24 | { 25 | return DIRECTORY_SEPARATOR === '\\'; 26 | } 27 | 28 | /** 29 | * 获取指定的控制台脚本 30 | * 31 | * @param string $cmd 32 | * 33 | * @return string 34 | */ 35 | public static function getCommander($cmd = 'yii') 36 | { 37 | return self::isRunningOnWindows() ? "@root/{$cmd}.bat" : "@root/{$cmd}"; 38 | } 39 | 40 | /** 41 | * 执行控制台命令 42 | * 43 | * @param string $cmd 需要执行的命令 44 | * @param bool $show 是否显示输出信息,默认显示 45 | */ 46 | public static function run($cmd, $show = true) 47 | { 48 | if (self::isRunningOnWindows()) { 49 | $cmd = str_replace("\\", "\\\\", $cmd); 50 | } 51 | $handler = popen($cmd, 'r'); 52 | while (!feof($handler)) { 53 | $show ? self::info(fgets($handler), 1) : fgets($handler); 54 | } 55 | pclose($handler); 56 | } 57 | 58 | /** 59 | * 显示成功信息 60 | * 61 | * @param string $message 需要显示的信息 62 | * @param int $rnCount 换行总数 63 | */ 64 | public static function success($message, $rnCount = 0) 65 | { 66 | if (Yii::$app instanceof Application) { 67 | self::writeColorMessage($message, 'green', $rnCount); 68 | } else { 69 | Console::stdout($message); 70 | } 71 | } 72 | 73 | /** 74 | * 显示错误信息 75 | * 76 | * @param string $message 需要显示的信息 77 | * @param int $rnCount 换行总数 78 | */ 79 | public static function error($message, $rnCount = 0) 80 | { 81 | if (Yii::$app instanceof Application) { 82 | self::writeColorMessage($message, 'red', $rnCount); 83 | } else { 84 | Console::stderr($message); 85 | } 86 | } 87 | 88 | /** 89 | * 显示提示信息 90 | * 91 | * @param string $message 需要显示的信息 92 | * @param int $rnCount 换行总数 93 | */ 94 | public static function info($message, $rnCount = 0) 95 | { 96 | if (Yii::$app instanceof Application) { 97 | self::writeColorMessage($message, 'orange', $rnCount); 98 | } else { 99 | Console::stdout($message); 100 | } 101 | } 102 | 103 | /** 104 | * 显示彩色的信息 105 | * 106 | * @param string $message 需要显示的信息 107 | * @param string $color 文字颜色 108 | * @param int $rnCount 换行总数 109 | */ 110 | public static function writeColorMessage($message, $color, $rnCount = 0) 111 | { 112 | $message = "$message"; 113 | self::writeMessage($message, $rnCount); 114 | } 115 | 116 | /** 117 | * 显示信息 118 | * 119 | * @param string $message 需要显示的信息 120 | * @param int $rnCount 换行总数 121 | */ 122 | public static function writeMessage($message, $rnCount = 0) 123 | { 124 | $message = str_replace(["'", "\n"], ["\"", ""], $message) . str_repeat("
", $rnCount); 125 | self::writeScript("parent.writeMessage('$message')"); 126 | } 127 | 128 | /** 129 | * 显示js脚本 130 | * 131 | * @param string $js 132 | */ 133 | private static function writeScript($js) 134 | { 135 | echo ""; 136 | ob_flush(); 137 | flush(); 138 | } 139 | 140 | } -------------------------------------------------------------------------------- /helpers/DateTimeHelper.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class DateTimeHelper 16 | { 17 | 18 | const HOUR = 0; 19 | const MINUTE = 1; 20 | const SECOND = 2; 21 | const YEAR = 3; 22 | const MONTH = 4; 23 | const DAY = 5; 24 | const WEEK = 6; 25 | 26 | /** 27 | * 获取时间单位列表 28 | * 29 | * @return array 30 | */ 31 | public static function getTimeUnitList() 32 | { 33 | return [ 34 | self::HOUR => Yii::t('wocenter/app', 'Hour'), 35 | self::MINUTE => Yii::t('wocenter/app', 'Minute'), 36 | self::SECOND => Yii::t('wocenter/app', 'Second'), 37 | self::YEAR => Yii::t('wocenter/app', 'Year'), 38 | self::MONTH => Yii::t('wocenter/app', 'Month'), 39 | self::DAY => Yii::t('wocenter/app', 'Day'), 40 | self::WEEK => Yii::t('wocenter/app', 'Week'), 41 | ]; 42 | } 43 | 44 | /** 45 | * 获取时间单位值 46 | * 47 | * @param string|integer $key 48 | * 49 | * @return mixed 50 | */ 51 | public static function getTimeUnitValue($key = null) 52 | { 53 | return ArrayHelper::getValue(static::getTimeUnitList(), $key); 54 | } 55 | 56 | /** 57 | * 标准化格式时间戳 58 | * 59 | * @param integer $timestamp 需要格式化的日期时间 60 | * @param string $format 时间格式,默认为 Y-m-d H:i:s 61 | * 62 | * @return string|null 完整的时间显示 63 | */ 64 | public static function timeFormat($timestamp = null, $format = 'Y-m-d H:i:s') 65 | { 66 | if ($timestamp === null) { 67 | $timestamp = time(); 68 | } elseif (empty($timestamp)) { 69 | return null; 70 | } else { 71 | $timestamp = intval($timestamp); 72 | } 73 | if (strncmp($format, 'php:', 4) === 0) { 74 | $format = substr($format, 4); 75 | } 76 | 77 | return date($format, $timestamp); 78 | } 79 | 80 | /** 81 | * 格式化为友好时间格式 82 | * 83 | * @param integer $timestamp 时间戳 84 | * 85 | * @return string 友好时间格式 86 | */ 87 | public static function timeFriendly($timestamp = null) 88 | { 89 | if ($timestamp == null) { 90 | return 'N/A'; 91 | } 92 | 93 | // 获取当前时间戳 94 | $currentTime = time(); 95 | 96 | // 获取当天0点时间戳 97 | $todayZero = strtotime('today'); 98 | 99 | // 获取昨天时间戳 100 | $yesterday = strtotime('-1 day', $todayZero); 101 | 102 | // 获取前天时间戳 103 | $beforeYesterday = strtotime('-1 day', $yesterday); 104 | 105 | // 获取明天0点时间戳 106 | $tomorrow = strtotime('+1 day', $todayZero); 107 | 108 | // 获取后天0点时间戳 109 | $afterTomorrow = strtotime('+1 day', $tomorrow); 110 | 111 | // 获取一天的时间戳 112 | $oneDayTimestamp = 3600 * 24; 113 | 114 | //当年时间戳 115 | $yearDiff = $currentTime - strtotime("-1 year"); 116 | 117 | // 时间差 118 | $timeDiff = $currentTime - $timestamp; 119 | 120 | switch (true) { 121 | case $timestamp >= $afterTomorrow && $timestamp < $afterTomorrow + $oneDayTimestamp : 122 | return Yii::t('wocenter/app', 'The day after tomorrow {time}', ['time' => date('H:i', $timestamp)]); 123 | case $timestamp >= $tomorrow && $timestamp < $afterTomorrow : 124 | return Yii::t('wocenter/app', 'Tomorrow {time}', ['time' => date('H:i', $timestamp)]); 125 | case $timestamp >= $todayZero && $timestamp < $tomorrow : 126 | if ($timeDiff < (3600 * 8)) { 127 | return Yii::$app->getFormatter()->asRelativeTime($timestamp); 128 | } else { 129 | return Yii::t('wocenter/app', 'Today {time}', ['time' => date('H:i', $timestamp)]); 130 | } 131 | case $timestamp >= $yesterday && $timestamp < $todayZero : 132 | return Yii::t('wocenter/app', 'Yesterday {time}', ['time' => date('H:i', $timestamp)]); 133 | case $timestamp >= $beforeYesterday && $timestamp < $yesterday : 134 | return Yii::t('wocenter/app', 'The day before yesterday {time}', ['time' => date('H:i', $timestamp)]); 135 | default : 136 | if ($timeDiff > $yearDiff) { 137 | return date('Y-m-d H:i', $timestamp); 138 | } else { 139 | return date('m-d H:i', $timestamp); 140 | } 141 | } 142 | } 143 | 144 | /** 145 | * 根据时间单位,比较两个时间差。即$timestamp距离$time(默认为当前时间)已过多久 146 | * 147 | * @param integer $timestamp 需要比较的时间 148 | * @param int|string $type 需要比较的时间单位,默认为秒, [时,分,秒,年,月,日,周]; 149 | * @param null|integer $time 被比较的时间,默认为当前时间 150 | * 151 | * @return int 两个时间差 152 | */ 153 | public static function getTimeAgo($timestamp = 1, $type = self::SECOND, $time = null) 154 | { 155 | if (empty($time)) { 156 | $time = time(); 157 | } 158 | switch ($type) { 159 | case self::HOUR: 160 | $result = $time - $timestamp * 60 * 60; 161 | break; 162 | case self::MINUTE: 163 | $result = $time - $timestamp * 60; 164 | break; 165 | case self::SECOND: 166 | $result = $time - $timestamp; 167 | break; 168 | case self::YEAR: 169 | $result = strtotime('-' . $timestamp . ' year', $time); 170 | break; 171 | case self::MONTH: 172 | $result = strtotime('-' . $timestamp . ' month', $time); 173 | break; 174 | case self::DAY: 175 | $result = strtotime('-' . $timestamp . ' day', $time); 176 | break; 177 | case self::WEEK: 178 | $result = strtotime('-' . ($timestamp * 7) . ' day', $time); 179 | break; 180 | default: 181 | $result = $time - $timestamp; 182 | } 183 | 184 | return $result; 185 | } 186 | 187 | /** 188 | * 根据时间单位,获取一个给定时间的未来时间戳。即$timestamp后$time的时间戳 189 | * 190 | * @param integer $timestamp 需要比较的时间 191 | * @param int|string $type 需要比较的时间单位 [时,分,秒,年,月,日,周]; 192 | * @param null|integer $time 已该时间戳为起点 193 | * - null,则为time() 194 | * 195 | * @return int 196 | */ 197 | public static function getAfterTime($timestamp = 1, $type = self::SECOND, $time = null) 198 | { 199 | if (empty($time)) { 200 | $time = time(); 201 | } 202 | switch ($type) { 203 | case self::MINUTE: 204 | $result = $time + $timestamp * 60; 205 | break; 206 | case self::HOUR: 207 | $result = $time + $timestamp * 60 * 60; 208 | break; 209 | case self::SECOND: 210 | $result = $time + $timestamp; 211 | break; 212 | case self::YEAR: 213 | $result = strtotime('+' . $timestamp . ' year', $time); 214 | break; 215 | case self::MONTH: 216 | $result = strtotime('+' . $timestamp . ' month', $time); 217 | break; 218 | case self::DAY: 219 | $result = strtotime('+' . $timestamp . ' day', $time); 220 | break; 221 | case self::WEEK: 222 | $result = strtotime('+' . ($timestamp * 7) . ' day', $time); 223 | break; 224 | default: 225 | $result = $time + $timestamp; 226 | } 227 | 228 | return $result; 229 | } 230 | 231 | /** 232 | * 解析并返回PHP DateTime能够理解的时间日期格式 233 | * 234 | * @param string $format ICU或php类型的时间日期格式 235 | * 配置示例: 236 | * ```php 237 | * 'MM/dd/yyyy' // ICU格式 238 | * 'php:m/d/Y' // PHP格式 239 | * ``` 240 | * @param string $type 时间日期类型,可选值有: `date`, `time`, `datetime` 241 | * 242 | * @return string 243 | * @throws InvalidConfigException 244 | */ 245 | public static function parseFormat($format, $type) 246 | { 247 | if (strncmp($format, 'php:', 4) === 0) { 248 | return substr($format, 4); 249 | } elseif ($format != '') { 250 | return FormatConverter::convertDateIcuToPhp($format, $type); 251 | } else { 252 | throw new InvalidConfigException("Error parsing '{$type}' format."); 253 | } 254 | } 255 | 256 | } 257 | -------------------------------------------------------------------------------- /helpers/FileHelper.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class FileHelper extends BaseFileHelper 14 | { 15 | 16 | public static function buildPath($paths, $withStart = false, $withEnd = false) 17 | { 18 | $res = ''; 19 | 20 | foreach ($paths as $path) { 21 | $res .= $path . DIRECTORY_SEPARATOR; 22 | } 23 | if ($withStart) { 24 | $res = DIRECTORY_SEPARATOR . $res; 25 | } 26 | if (!$withEnd) { 27 | $res = rtrim($res, DIRECTORY_SEPARATOR); 28 | } 29 | 30 | return $res; 31 | } 32 | 33 | public static function isDir($path) 34 | { 35 | return is_dir($path); 36 | } 37 | 38 | public static function exist($path) 39 | { 40 | if (is_array($path)) { 41 | $path = self::buildPath($path); 42 | } 43 | $path = self::normalizePath($path); 44 | 45 | return file_exists($path); 46 | } 47 | 48 | public static function getFiles($path, $prefix = null) 49 | { 50 | if (is_array($path)) { 51 | $path = self::buildPath($path); 52 | } 53 | 54 | if (!is_dir($path)) { 55 | // var_dump($path); 56 | return []; 57 | } 58 | 59 | $files = scandir($path); 60 | if ($prefix == null) { 61 | return $files; 62 | } 63 | 64 | $res = []; 65 | foreach ($files as $file) { 66 | if (strpos($file, $prefix) === 0) { 67 | $res[] = $file; 68 | } 69 | } 70 | 71 | return $res; 72 | } 73 | 74 | public static function createFile($filePath, $content, $mode = 0777): bool 75 | { 76 | if (@file_put_contents($filePath, $content, LOCK_EX) !== false) { 77 | if ($mode !== null) { 78 | @chmod($filePath, $mode); 79 | } 80 | 81 | return @touch($filePath); 82 | } else { 83 | $error = error_get_last(); 84 | Yii::warning("Unable to write file '{$filePath}': {$error['message']}", __METHOD__); 85 | 86 | return false; 87 | } 88 | } 89 | 90 | public static function removeFile($filePath) 91 | { 92 | return \yii\helpers\FileHelper::unlink($filePath); 93 | } 94 | 95 | public static function readFile($filePath) 96 | { 97 | if (is_array($filePath)) { 98 | $filePath = self::buildPath($filePath); 99 | } 100 | 101 | return file_get_contents($filePath); 102 | } 103 | 104 | public static function writeFile($filePath, $content, $mode = 'w') 105 | { 106 | if (is_array($filePath)) { 107 | $filePath = self::buildPath($filePath); 108 | } 109 | 110 | $f = fopen($filePath, $mode); 111 | fwrite($f, $content); 112 | fclose($f); 113 | } 114 | 115 | public static function createDir($dirPath) 116 | { 117 | \yii\helpers\FileHelper::createDirectory($dirPath); 118 | } 119 | 120 | public static function removeDir($dirPath) 121 | { 122 | \yii\helpers\FileHelper::removeDirectory($dirPath); 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /helpers/SecurityHelper.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class SecurityHelper 11 | { 12 | 13 | /** 14 | * 保密邮箱地址 15 | * 16 | * @param string $str 邮箱地址 17 | * 18 | * @return string 19 | */ 20 | public static function markEmail($str): string 21 | { 22 | list($first, $second) = explode('@', $str); 23 | $a = strlen($first) - 3; 24 | 25 | return preg_replace('|(?<=.{1})(.{' . $a . '}).*?|', str_pad('', $a, '*'), $first, 1) . '@' . $second; 26 | } 27 | 28 | /** 29 | * 保密手机号 30 | * 31 | * @param string $str 手机号 32 | * 33 | * @return string 34 | */ 35 | public static function markMobile($str): string 36 | { 37 | if (!empty($str)) { 38 | return preg_replace('|(?<=.{3})(.{4}).*?|', str_pad('', 4, '*'), $str, 1); 39 | } 40 | 41 | return $str; 42 | } 43 | 44 | /** 45 | * 保密字符串 46 | * 47 | * @param string $str 48 | * 49 | * @return string 50 | */ 51 | public static function markString($str): string 52 | { 53 | $str_len = strlen($str); 54 | $start = 6 < $str_len ? intval($str_len / 5) : $str_len - 4; 55 | $end = $str_len - $start * 3; 56 | 57 | return preg_replace('|(?<=.{' . $start . '})(.{' . $end . '}).*?|', str_pad('', $end, '*'), $str, 1); 58 | } 59 | 60 | /** 61 | * 获取哈希值 62 | * 63 | * @param string $message 64 | * @param string $salt 65 | * 66 | * @return string 67 | */ 68 | public static function hash(string $message, $salt = "wocenter"): string 69 | { 70 | $s01 = $message . $salt; 71 | $s02 = md5($s01) . $salt; 72 | $s03 = sha1($s01) . md5($s02) . $salt; 73 | $s04 = $salt . md5($s03) . $salt . $s02; 74 | $s05 = $salt . sha1($s04) . md5($s04) . crc32($salt . $s04); 75 | 76 | return md5($s05); 77 | } 78 | 79 | /** 80 | * 系统解密方法 81 | * 82 | * @param string $data 要解密的字符串 83 | * @param string $key 加密密钥 84 | * 85 | * @return string 86 | */ 87 | public static function decrypt($data = '', $key = ''): string 88 | { 89 | $key = md5(empty($key) ? \Yii::$app->getRequest()->cookieValidationKey : $key); 90 | $data = str_replace(['-', '_'], ['+', '/'], $data); 91 | $mod4 = strlen($data) % 4; 92 | if ($mod4) { 93 | $data .= substr('====', $mod4); 94 | } 95 | $data = base64_decode($data); 96 | $expire = substr($data, 0, 10); 97 | $data = substr($data, 10); 98 | 99 | if ($expire > 0 && $expire < time()) { 100 | return ''; 101 | } 102 | $x = 0; 103 | $len = strlen($data); 104 | $l = strlen($key); 105 | $char = $str = ''; 106 | 107 | for ($i = 0; $i < $len; $i++) { 108 | if ($x == $l) { 109 | $x = 0; 110 | } 111 | $char .= substr($key, $x, 1); 112 | $x++; 113 | } 114 | 115 | for ($i = 0; $i < $len; $i++) { 116 | if (ord(substr($data, $i, 1)) < ord(substr($char, $i, 1))) { 117 | $str .= chr((ord(substr($data, $i, 1)) + 256) - ord(substr($char, $i, 1))); 118 | } else { 119 | $str .= chr(ord(substr($data, $i, 1)) - ord(substr($char, $i, 1))); 120 | } 121 | } 122 | 123 | return base64_decode($str); 124 | } 125 | 126 | /** 127 | * 系统加密方法 128 | * 129 | * @param string $data 要加密的字符串 130 | * @param string $key 加密密钥 131 | * @param integer $expire 过期时间 单位 秒 132 | * 133 | * @return string 134 | */ 135 | public static function encrypt($data = '', $key = '', $expire = 0): string 136 | { 137 | $key = md5(empty($key) ? \Yii::$app->getRequest()->cookieValidationKey : $key); 138 | $data = base64_encode($data); 139 | $x = 0; 140 | $len = strlen($data); 141 | $l = strlen($key); 142 | $char = ''; 143 | 144 | for ($i = 0; $i < $len; $i++) { 145 | if ($x == $l) { 146 | $x = 0; 147 | } 148 | $char .= substr($key, $x, 1); 149 | $x++; 150 | } 151 | 152 | $str = sprintf('%010d', $expire ? $expire + time() : 0); 153 | 154 | for ($i = 0; $i < $len; $i++) { 155 | $str .= chr(ord(substr($data, $i, 1)) + (ord(substr($char, $i, 1))) % 256); 156 | } 157 | 158 | return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($str)); 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /helpers/TreeHelper.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class TreeHelper 16 | { 17 | 18 | /** 19 | * 获取数据提供器所有父级ID数据 20 | * 21 | * @param ActiveRecord|array $dataProvider 数据提供器 22 | * @param integer $currentPid 当前数据的父级ID,从该值开始获取父级ID数据,直到$`rootId`值, 23 | * 该值仅在`$dataProvider`为数组类型时生效 24 | * @param string $pkField 主键字段 25 | * @param string $parentField 父级字段 26 | * @param integer $rootId 顶级ID,默认为`0`,返回的父级ID数据获取到此顶级ID后则停止获取 27 | * 28 | * @return array 29 | * @throws InvalidConfigException 30 | * @throws NotFoundHttpException 31 | */ 32 | public static function getParentIds($dataProvider, int $currentPid = 0, $pkField = 'id', $parentField = 'parent_id', int $rootId = 0) 33 | { 34 | // 模型数据提供器 35 | if ($dataProvider instanceof ActiveRecord) { 36 | foreach ([$pkField, $parentField] as $field) { 37 | if (!isset($dataProvider->$field)) { 38 | throw new InvalidConfigException("The `{$field}` property of model class: `{$dataProvider::className()}` does not exists"); 39 | } 40 | } 41 | // 必须确保模型数据提供器存在有效数据 42 | if (empty($dataProvider->$pkField)) { 43 | $method = __METHOD__ . '()'; 44 | throw new NotFoundHttpException("{$dataProvider::className()}()必须存在有效数据后才能执行操作:{$method}"); 45 | } 46 | $currentPid = $dataProvider->$parentField; 47 | // 增加对模型内置[[getAll()]]方法的支持,可避免多次查询数据库,优化性能 48 | if ($dataProvider->hasMethod('getAll')) { 49 | $dataProvider = $dataProvider->getAll(); 50 | } 51 | } // 数组数据提供器 52 | elseif (is_array($dataProvider)) { 53 | } else { 54 | throw new InvalidConfigException("The model class: `{$dataProvider}` does not exists"); 55 | } 56 | 57 | if (0 === $currentPid) { 58 | return []; 59 | } 60 | 61 | if (is_array($dataProvider)) { 62 | return self::_getParentIdsByArray($dataProvider, $currentPid, $pkField, $parentField, $rootId); 63 | } else { 64 | return self::_getParentIdsByModel($dataProvider, $pkField, $parentField, $rootId); 65 | } 66 | } 67 | 68 | private static function _getParentIdsByArray(array $list, int $currentPid, string $pkField, string $parentField, int $rootId): array 69 | { 70 | $_parentIds = []; 71 | if ($list) { 72 | while ($currentPid !== $rootId) { 73 | array_unshift($_parentIds, $currentPid); 74 | $model = ArrayHelper::listSearch($list, [ 75 | $pkField => $currentPid, 76 | ]); 77 | $currentPid = $model ? (int)$model[0][$parentField] : $rootId; 78 | } 79 | } 80 | 81 | return $_parentIds; 82 | } 83 | 84 | private static function _getParentIdsByModel(ActiveRecord $model, string $pkField, string $parentField, int $rootId): array 85 | { 86 | $_parentIds = []; 87 | while ($model !== null) { 88 | if ($model->$parentField !== $rootId) { 89 | array_unshift($_parentIds, $model->$parentField); 90 | $model = $model::findOne([$pkField => $model->$parentField]); 91 | } else { 92 | $model = null; 93 | } 94 | } 95 | 96 | return $_parentIds; 97 | } 98 | 99 | /** 100 | * 获取数据提供器所有子类ID数据 101 | * 102 | * @param ActiveRecord|array $dataProvider 数据提供器 103 | * @param integer $currentId 获取该值的所有子类ID数据,为'0'时,表示获取所有顶级数据 104 | * @param string $pkField 主键字段 105 | * @param string $parentField 父级字段 106 | * 107 | * @return array 108 | * @throws InvalidConfigException 109 | * @throws NotFoundHttpException 110 | */ 111 | public static function getChildrenIds($dataProvider, int $currentId = 0, $pkField = 'id', $parentField = 'parent_id') 112 | { 113 | // 模型数据提供器 114 | if ($dataProvider instanceof ActiveRecord) { 115 | foreach ([$pkField, $parentField] as $field) { 116 | if (!isset($dataProvider->$field)) { 117 | throw new InvalidConfigException("The `{$field}` property of model class: `{$dataProvider::className()}` does not exists"); 118 | } 119 | } 120 | // 必须确保模型数据提供器存在有效数据 121 | if (empty($dataProvider->$pkField)) { 122 | $method = __METHOD__ . '()'; 123 | throw new NotFoundHttpException("{$dataProvider::className()}()必须存在有效数据后才能执行操作:{$method}"); 124 | } 125 | // 增加对模型内置[[getAll()]]方法的支持,可避免多次查询数据库,优化性能 126 | if ($dataProvider->hasMethod('getAll')) { 127 | $dataProvider = $dataProvider->getAll(); 128 | } 129 | } // 数组数据提供器 130 | elseif (is_array($dataProvider)) { 131 | } else { 132 | throw new InvalidConfigException("The model class: `{$dataProvider}` does not exists"); 133 | } 134 | 135 | if (is_array($dataProvider)) { 136 | return self::_getChildrenIdsByArray($dataProvider, $currentId, $pkField, $parentField); 137 | } else { 138 | return self::_getChildrenIdsByModel($dataProvider, $currentId, $pkField, $parentField); 139 | } 140 | } 141 | 142 | private static function _getChildrenIdsByArray(array $list, int $currentId, string $pkField, string $parentField): array 143 | { 144 | $_childrenIds = []; 145 | if ($list) { 146 | $children = ArrayHelper::listSearch($list, [ 147 | $parentField => $currentId, 148 | ]); 149 | while (count($children) > 0) { 150 | $first = array_shift($children); 151 | $_childrenIds[] = (int)$first[$pkField]; 152 | $next = ArrayHelper::listSearch($list, [ 153 | $parentField => $first[$pkField], 154 | ]); 155 | if (count($next) > 0) { 156 | $children = array_merge($children, $next); 157 | } 158 | } 159 | } 160 | 161 | return $_childrenIds; 162 | } 163 | 164 | private static function _getChildrenIdsByModel(ActiveRecord $model, int $currentId, string $pkField, string $parentField): array 165 | { 166 | $_childrenIds = []; 167 | $children = $model::findAll([$parentField => $currentId]); 168 | while (count($children) > 0) { 169 | $first = array_shift($children); 170 | $_childrenIds[] = $first->$pkField; 171 | $next = $model::findAll([$parentField => $first->$pkField]); 172 | if (count($next) > 0) { 173 | $children = array_merge($children, $next); 174 | } 175 | } 176 | 177 | return $_childrenIds; 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /helpers/UrlHelper.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class UrlHelper 11 | { 12 | 13 | /** 14 | * 将URL中的参数取出来放到数组里 15 | * 16 | * @param string $query 17 | * 18 | * @return array $params 19 | */ 20 | public static function convertUrlQuery($query) 21 | { 22 | if (empty($query)) { 23 | return []; 24 | } 25 | $queryParts = explode('&', $query); 26 | $params = []; 27 | foreach ($queryParts as $param) { 28 | $item = explode('=', $param); 29 | // FIXED:2016-07-13 解析数组型参数 30 | $pos = strpos($item[0], '['); 31 | if ($pos) { 32 | $params[substr($item[0], 0, $pos)][substr($item[0], $pos + 1, -1)] = $item[1]; 33 | } else { 34 | $params[$item[0]] = $item[1]; 35 | } 36 | } 37 | 38 | return $params; 39 | } 40 | 41 | /** 42 | * 将 参数数组 变回 字符串形式的参数格式 43 | * 44 | * @param array $array_query 45 | * 46 | * @return string 47 | */ 48 | public static function getUrlQuery(array $array_query) 49 | { 50 | $tmp = []; 51 | foreach ($array_query as $name => $value) { 52 | if (is_array($value)) { 53 | foreach ($value as $k => $v) { 54 | $tmp[] = "{$name}[{$k}]={$v}"; 55 | } 56 | } else { 57 | $tmp[] = "{$name}={$value}"; 58 | } 59 | } 60 | 61 | return implode('&', $tmp); 62 | } 63 | 64 | /** 65 | * 删除url地址里指定的参数 66 | * 67 | * @param string $url 68 | * @param string $params 待删除参数名,不支持删除数组参数 69 | * @param boolean $format 是否返回格式化后干净的url地址,默认格式化 70 | * 71 | * @return string|array 干净的url或包含path、query等信息的数组 72 | */ 73 | public static function unsetParams($url = '', $params = '', $format = true) 74 | { 75 | if (strpos($url, '?') === false) { 76 | return $url; 77 | } 78 | $parseUrl = static::parseUrl($url); 79 | if (isset($parseUrl['query'])) { 80 | foreach (explode(',', $params) as $param) { 81 | if (isset($parseUrl['query'][$param])) { 82 | unset($parseUrl['query'][$param]); 83 | } 84 | } 85 | if ($format) { 86 | // 删除参数后,存在其他参数则重新组装URL,否则参数为空 87 | $parseUrl['query'] = !empty($parseUrl['query']) ? ('?' . static::getUrlQuery($parseUrl['query'])) : ''; 88 | } 89 | } else { 90 | $parseUrl['query'] = ''; 91 | } 92 | 93 | return $format ? ($parseUrl['path'] . $parseUrl['query']) : $parseUrl; 94 | } 95 | 96 | /** 97 | * 解析url地址,返回的query请求参数为数组格式 98 | * 99 | * @param string $url 100 | * 101 | * @return array ['path', 'query'] 102 | */ 103 | public static function parseUrl($url) 104 | { 105 | $parseUrl = parse_url($url); 106 | if (isset($parseUrl['query'])) { 107 | // 修复Pjax参数。该参数带#符号,导致该参数后的其他参数被标识为fragment 108 | if (strpos($parseUrl['query'], '_pjax') !== false && isset($parseUrl['fragment'])) { 109 | $parseUrl['query'] .= $parseUrl['fragment']; 110 | unset($parseUrl['fragment']); 111 | } 112 | $parseUrl['query'] = self::convertUrlQuery($parseUrl['query']); 113 | } 114 | 115 | return $parseUrl; 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /interfaces/ConfigProviderInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface ConfigProviderInterface 11 | { 12 | 13 | /** 14 | * @var string 网站标题 15 | */ 16 | const WEB_SITE_TITLE = 'WEB_SITE_TITLE'; 17 | 18 | /** 19 | * @var string 网站描述 20 | */ 21 | const WEB_SITE_DESCRIPTION = 'WEB_SITE_DESCRIPTION'; 22 | 23 | /** 24 | * @var string 网站关键词 25 | */ 26 | const WEB_SITE_KEYWORD = 'WEB_SITE_KEYWORD'; 27 | 28 | /** 29 | * @var string 网站ICP 30 | */ 31 | const WEB_SITE_ICP = 'WEB_SITE_ICP'; 32 | 33 | /** 34 | * @var string 关闭网站 35 | */ 36 | const WEB_SITE_CLOSE = 'WEB_SITE_CLOSE'; 37 | 38 | /** 39 | * @var string 关闭网站提示语 40 | */ 41 | const WEB_SITE_CLOSE_TIPS = 'WEB_SITE_CLOSE_TIPS'; 42 | 43 | /** 44 | * @var int 字符串类型 45 | */ 46 | const TYPE_STRING = 1; 47 | 48 | /** 49 | * @var int 文本类型 50 | */ 51 | const TYPE_TEXT = 2; 52 | 53 | /** 54 | * @var int 下拉框类型 55 | */ 56 | const TYPE_SELECT = 3; 57 | 58 | /** 59 | * @var int 选择框类型 60 | */ 61 | const TYPE_CHECKBOX = 4; 62 | 63 | /** 64 | * @var int 单选项类型 65 | */ 66 | const TYPE_RADIO = 5; 67 | 68 | /** 69 | * @var int 看板类型 70 | */ 71 | const TYPE_KANBAN = 6; 72 | 73 | /** 74 | * @var int 日期时间类型 75 | */ 76 | const TYPE_DATETIME = 7; 77 | 78 | /** 79 | * @var int 日期类型 80 | */ 81 | const TYPE_DATE = 8; 82 | 83 | /** 84 | * @var int 时间类型 85 | */ 86 | const TYPE_TIME = 9; 87 | 88 | /** 89 | * @var int 不分组 90 | */ 91 | const CATEGORY_NONE = 0; 92 | 93 | /** 94 | * @var int 基础配置 95 | */ 96 | const CATEGORY_BASE = 1; 97 | 98 | /** 99 | * @var int 内容配置 100 | */ 101 | const CATEGORY_CONTENT = 2; 102 | 103 | /** 104 | * @var int 注册配置 105 | */ 106 | const CATEGORY_REGISTRATION = 3; 107 | 108 | /** 109 | * @var int 系统配置 110 | */ 111 | const CATEGORY_SYSTEM = 4; 112 | 113 | /** 114 | * @var int 安全配置 115 | */ 116 | const CATEGORY_SECURITY = 5; 117 | 118 | /** 119 | * 获取所有配置项,数组键名以配置ID为索引 120 | * 121 | * @example 122 | * ```php 123 | * [ 124 | * 'WEB_SITE_TITLE' => [], 125 | * 'WEB_SITE_DESCRIPTION' => [], 126 | * ] 127 | * ``` 128 | * 129 | * @return array 130 | */ 131 | public function getAll(); 132 | 133 | /** 134 | * 删除缓存 135 | */ 136 | public function clearCache(); 137 | 138 | } 139 | -------------------------------------------------------------------------------- /interfaces/ControllerInfoInterface.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | interface ControllerInfoInterface 19 | { 20 | 21 | /** 22 | * 获取扩展所属模块ID 23 | * 24 | * @return string 25 | */ 26 | public function getModuleId(); 27 | 28 | /** 29 | * 设置扩展所属模块ID 30 | * 31 | * @param string $moduleId 模块ID 32 | */ 33 | public function setModuleId($moduleId); 34 | 35 | /** 36 | * 获取数据库迁移路径 37 | * 38 | * @return string 39 | */ 40 | public function getMigrationPath(); 41 | 42 | /** 43 | * 设置数据库迁移路径 44 | * 45 | * @param string $migrationPath 数据库迁移路径 46 | */ 47 | public function setMigrationPath($migrationPath); 48 | 49 | /** 50 | * 获取扩展菜单信息 51 | * 52 | * @see MenuProvider::defaultMenuConfig() 53 | * 54 | * @return array 55 | */ 56 | public function getMenus(); 57 | 58 | } 59 | -------------------------------------------------------------------------------- /interfaces/ControllerRepositoryInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface ControllerRepositoryInterface 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /interfaces/DispatchManagerInterface.php: -------------------------------------------------------------------------------- 1 | 29 | */ 30 | interface DispatchManagerInterface 31 | { 32 | 33 | /** 34 | * 获取当前控制器的调度器配置 35 | * - 如果不存在用户自定义配置数据[[Controller::$dispatchMap]]或全局配置里不存在用户自定义配置数据, 36 | * 则返回控制器的默认调度器配置信息[[Controller::$defaultDispatches]], 37 | * 该属性在初始化阶段[[init()]]已赋值给[[$_defaultDispatches]]。 38 | * - 如果存在用户自定义配置数据[[Controller::$dispatchMap]]或全局配置里不存在用户自定义配置数据, 39 | * 则进一步验证配置数据是否规范。 40 | * 41 | * @return array 42 | */ 43 | public function getDispatchMap(): array; 44 | 45 | /** 46 | * 获取全局调度器配置 47 | * 48 | * @return array 49 | */ 50 | public function getConfig(): array; 51 | 52 | /** 53 | * 设置全局调度器配置 54 | * 55 | * @param array $config 调度器配置数据 56 | * 57 | * @return array 58 | */ 59 | public function setConfig(array $config): array; 60 | 61 | /** 62 | * 根据路由地址获取调度器,最经常使用的场景是在操作完成后,返回执行结果数据或视图 63 | * 赋值数据给前端,或获取指定调度器后,使用调度器内某些方法。 64 | * 65 | * @param null|string $route 调度路由,支持以下格式:'view', 'config-manager/view','system/config-manager/view' 66 | * 67 | * @return null|Dispatch|\wocenter\core\web\Dispatch 68 | * @throws InvalidRouteException 69 | */ 70 | public function getDispatch($route = null); 71 | 72 | /** 73 | * 创建调度器 74 | * 75 | * @param string $id 调度器ID 76 | * 77 | * @return null|Dispatch 78 | */ 79 | public function createDispatch($id); 80 | 81 | /** 82 | * 获取调度器需要渲染的视图文件路径 83 | * 84 | * @return string 85 | */ 86 | public function getViewPath(): string; 87 | 88 | /** 89 | * 获取当前主题的参数配置 90 | * 91 | * @return array 92 | */ 93 | public function getThemeConfig(): array; 94 | 95 | /** 96 | * 当前控制器所属的扩展信息 97 | * 98 | * @return RunningExtensionInterface 99 | */ 100 | public function getRunningExtension(); 101 | 102 | } 103 | -------------------------------------------------------------------------------- /interfaces/EnumInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface EnumInterface 11 | { 12 | 13 | /** 14 | * @var integer 不限 15 | */ 16 | const UNLIMITED = 999; 17 | 18 | /** 19 | * 获取列表 20 | * 21 | * @return array 22 | */ 23 | public static function list(); 24 | 25 | /** 26 | * 获取值 27 | * 28 | * @param string|\Closure|array $key 29 | * 30 | * @return mixed 31 | */ 32 | public static function value($key); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /interfaces/ExtensionInfoInterface.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | interface ExtensionInfoInterface 19 | { 20 | 21 | /** 22 | * @var integer 运行系统扩展 23 | */ 24 | const RUN_MODULE_EXTENSION = 0; 25 | 26 | /** 27 | * @var integer 运行开发者扩展 28 | */ 29 | const RUN_MODULE_DEVELOPER = 1; 30 | 31 | /** 32 | * @var integer 默认扩展不属于任何分类 33 | */ 34 | const CATEGORY_NONE = 'category_none'; 35 | 36 | /** 37 | * @var integer 首页控制器分类 38 | */ 39 | const CATEGORY_SITE = 'category_site'; 40 | 41 | /** 42 | * @var integer 系统模块分类 43 | */ 44 | const CATEGORY_SYSTEM = 'category_system'; 45 | 46 | /** 47 | * @var integer 扩展模块分类 48 | */ 49 | const CATEGORY_EXTENSION = 'category_extension'; 50 | 51 | /** 52 | * @var integer 菜单模块分类 53 | */ 54 | const CATEGORY_MENU = 'category_menu'; 55 | 56 | /** 57 | * @var integer 用户模块分类 58 | */ 59 | const CATEGORY_ACCOUNT = 'category_account'; 60 | 61 | /** 62 | * @var integer 通行证模块分类 63 | */ 64 | const CATEGORY_PASSPORT = 'category_passport'; 65 | 66 | /** 67 | * @var integer 安全模块分类 68 | */ 69 | const CATEGORY_SECURITY = 'category_security'; 70 | 71 | /** 72 | * 获取扩展唯一ID,不可重复 73 | * 74 | * @return string 75 | */ 76 | public function getUniqueId(); 77 | 78 | /** 79 | * 获取扩展唯一名称,不可重复 80 | * 81 | * @return string 82 | */ 83 | public function getUniqueName(); 84 | 85 | /** 86 | * 安装 87 | * 88 | * @return boolean 89 | */ 90 | public function install(); 91 | 92 | /** 93 | * 卸载 94 | * 95 | * @return boolean 96 | */ 97 | public function uninstall(); 98 | 99 | /** 100 | * 升级 101 | * 102 | * @return boolean 103 | */ 104 | public function upgrade(); 105 | 106 | /** 107 | * 版本 108 | * 109 | * @return string 110 | */ 111 | public function getVersion(); 112 | 113 | /** 114 | * 获取扩展所需依赖 115 | * 116 | * @return array 117 | */ 118 | public function getDepends(); 119 | 120 | /** 121 | * 获取扩展所需的composer包 122 | * 123 | * @return array 124 | */ 125 | public function getRequirePackages(); 126 | 127 | /** 128 | * 获取扩展所属类型 129 | * 130 | * @return string|null 131 | */ 132 | public function getCategory(); 133 | 134 | /** 135 | * 获取扩展配置信息允许的键名,用于过滤非法的配置数据 136 | * 137 | * @return array 138 | */ 139 | public function getConfigKey(); 140 | 141 | /** 142 | * 获取扩展配置信息 143 | * 可能包含的键名如下: 144 | * - `components` 145 | * - `params` 146 | * - `modules` 147 | * - `controllerMap` 148 | * 详情请查看[[getConfigKey()]] 149 | * @see getConfigKey() 150 | * 151 | * @return array 152 | */ 153 | public function getConfig(); 154 | 155 | /** 156 | * 获取扩展公共配置信息允许的键名,用于过滤非法的配置数据 157 | * 158 | * @return array 159 | */ 160 | public function getCommonConfigKey(); 161 | 162 | /** 163 | * 获取扩展公共配置信息 164 | * 可能包含的键名如下: 165 | * - `components` 166 | * - `params` 167 | * - `modules` 168 | * - `controllerMap` 169 | * 详情请查看[[getCommonConfigKey()]] 170 | * @see getCommonConfigKey() 171 | * 172 | * @return array 173 | */ 174 | public function getCommonConfig(); 175 | 176 | } 177 | -------------------------------------------------------------------------------- /interfaces/ExtensionRepositoryInterface.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | interface ExtensionRepositoryInterface 17 | { 18 | 19 | /** 20 | * 获取扩展详情,主要用于管理和安装 21 | * 22 | * @param string $extensionName 扩展名称 23 | * 24 | * @return ActiveRecord 25 | * @throws NotFoundHttpException 26 | */ 27 | public function getInfo($extensionName); 28 | 29 | /** 30 | * 获取所有已经安装的扩展数据,并以扩展名为索引 31 | * 32 | * @return array 33 | * [ 34 | * {uniqueName} => [], 35 | * ] 36 | */ 37 | public function getAll(); 38 | 39 | /** 40 | * 获取扩展模型 41 | * 42 | * @return ActiveRecord 43 | */ 44 | public function getModel(); 45 | 46 | /** 47 | * 设置扩展模型 48 | * 49 | * @param null|string|array $config 50 | */ 51 | public function setModel($config = []); 52 | 53 | } 54 | -------------------------------------------------------------------------------- /interfaces/MenuProviderInterface.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface MenuProviderInterface 18 | { 19 | 20 | /** 21 | * 菜单创建者为用户 22 | */ 23 | const CREATE_TYPE_BY_USER = 0; 24 | 25 | /** 26 | * 菜单创建者为扩展 27 | */ 28 | const CREATE_TYPE_BY_EXTENSION = 1; 29 | 30 | /** 31 | * 获取所有菜单数据 32 | * 33 | * @param null|int $level 菜单级别 34 | * 35 | * @return array 36 | * - 当$level不为`null`时,则获取指定菜单级别的菜单数据,并返回以$level值为索引的菜单数据分组 37 | * - 默认返回以主键值为索引的菜单数据分组 38 | */ 39 | public function getAll($level = null): array; 40 | 41 | /** 42 | * 删除缓存 43 | */ 44 | public function clearCache(); 45 | 46 | /** 47 | * 获取菜单模型类 48 | * 49 | * @return ActiveRecord 50 | */ 51 | public function getModel(); 52 | 53 | /** 54 | * 设置菜单模型类 55 | * 56 | * @param array $config 57 | * 58 | * @throws InvalidConfigException 59 | */ 60 | public function setModel($config = []); 61 | 62 | /** 63 | * 获取菜单配置数据 64 | * 65 | * @return array 66 | * [ 67 | * {app} => [], 68 | * ] 69 | */ 70 | public function getMenuConfig(); 71 | 72 | /** 73 | * 设置菜单配置数据 74 | * 75 | * @param array $config 菜单配置数据,该数据为未写入数据库的菜单数组数据, 76 | * 一般为系统默认的菜单数据或扩展配置文件里的菜单数据 77 | * @see ModularityInfoInterface::getMenus() 78 | * @see ControllerInfoInterface::getMenus() 79 | * 80 | * [ 81 | * {app} => [], 82 | * ] 83 | */ 84 | public function setMenuConfig($config = []); 85 | 86 | } 87 | -------------------------------------------------------------------------------- /interfaces/ModularityInfoInterface.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | interface ModularityInfoInterface 19 | { 20 | 21 | /** 22 | * 获取模块菜单信息 23 | * 24 | * @see MenuProvider::defaultMenuConfig() 25 | * 26 | * @return array 27 | */ 28 | public function getMenus(); 29 | 30 | /** 31 | * 获取数据库迁移路径 32 | * 33 | * @return string 34 | */ 35 | public function getMigrationPath(); 36 | 37 | /** 38 | * 设置数据库迁移路径 39 | * 40 | * @param string $migrationPath 数据库迁移路径 41 | */ 42 | public function setMigrationPath($migrationPath); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /interfaces/ModularityRepositoryInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface ModularityRepositoryInterface 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /interfaces/RunningExtensionInterface.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | interface RunningExtensionInterface 20 | { 21 | 22 | public function __construct(Controller $controller, array $config = []); 23 | 24 | /** 25 | * 当前控制器的上级命名空间 26 | * 27 | * @return string 28 | */ 29 | public function getNamespace():string; 30 | 31 | /** 32 | * 是否属于扩展内的控制器 33 | * 34 | * @return bool 35 | */ 36 | public function isExtensionController(): bool; 37 | 38 | /** 39 | * 控制器所属扩展的本地配置文件信息 40 | * 41 | * @return ExtensionInfo|object 42 | */ 43 | public function getInfo(); 44 | 45 | /** 46 | * 控制器所属扩展的数据库配置信息,目前主要是获取扩展当前的运行模式。 47 | * 数据库配置信息里必须包含以下字段: 48 | * - `run`: 扩展运行模式 49 | * 50 | * @return array 51 | */ 52 | public function getDbConfig(): array; 53 | 54 | /** 55 | * 获取当前控制器所属的扩展名称 56 | * 57 | * @return string 58 | */ 59 | public function getExtensionUniqueName(): string; 60 | 61 | /** 62 | * 控制器不属于任何一个扩展时,可用该方法为控制器指定一个所属扩展 63 | * WoCenter提供了一个默认的扩展类@see WoCenterExtension 64 | * 65 | * @return self|object 66 | */ 67 | public function defaultExtension(); 68 | 69 | } -------------------------------------------------------------------------------- /interfaces/ServiceInterface.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | interface ServiceInterface 14 | { 15 | 16 | /** 17 | * @return string 服务唯一ID 18 | */ 19 | public function getUniqueId(); 20 | 21 | /** 22 | * 核心子服务类 23 | * 24 | * @return array 25 | */ 26 | public function coreServices(); 27 | 28 | /** 29 | * 设置子服务 30 | * 31 | * @param string|array|callable $config 子服务配置信息 32 | * 33 | * @see \yii\BaseYii::createObject() 34 | */ 35 | public function setSubService($config); 36 | 37 | /** 38 | * 获取子服务 39 | * 40 | * @param string $serviceName 子服务名,不带后缀`Service` 41 | * 42 | * @return Service|null 43 | * @throws InvalidConfigException 44 | */ 45 | public function getSubService($serviceName); 46 | 47 | /** 48 | * 服务类执行后的相关信息 49 | * 50 | * @return mixed 51 | */ 52 | public function getInfo(); 53 | 54 | /** 55 | * 服务类执行后的相关数据 56 | * 57 | * @return mixed 58 | */ 59 | public function getData(); 60 | 61 | /** 62 | * 服务类执行后的结果数组 63 | * 64 | * @return array ['status', 'info', 'data'] 65 | */ 66 | public function getResult(); 67 | 68 | /** 69 | * 获取缓存时间间隔 70 | * 71 | * @return false|int 72 | */ 73 | public function getCacheDuration(); 74 | 75 | /** 76 | * 设置缓存时间间隔 77 | * 78 | * @param false|int $cacheDuration 当为`false`时,则删除缓存数据,默认缓存`一天` 79 | * 80 | * @return $this 81 | */ 82 | public function setCacheDuration($cacheDuration = 86400); 83 | 84 | /** 85 | * 删除缓存 86 | */ 87 | public function clearCache(); 88 | 89 | } 90 | -------------------------------------------------------------------------------- /interfaces/ThemeInfoInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface ThemeInfoInterface 13 | { 14 | 15 | /** 16 | * 获取主题视图路径 17 | * 18 | * @return string 19 | */ 20 | public function getViewPath(); 21 | 22 | /** 23 | * 设置主题视图路径 24 | * 25 | * @param string $viewPath 主题视图路径 26 | */ 27 | public function setViewPath($viewPath); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /interfaces/ThemeRepositoryInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface ThemeRepositoryInterface 11 | { 12 | 13 | /** 14 | * 获取当前主题名 15 | * 16 | * @return array|null 17 | */ 18 | public function getCurrentTheme(); 19 | 20 | /** 21 | * 获取所有激活的主题 22 | * 23 | * @return array|\yii\db\ActiveRecord[] 24 | */ 25 | public function getActiveTheme(); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /libs/IpLocation.php: -------------------------------------------------------------------------------- 1 | fp = 0; 48 | if (($this->fp = fopen(dirname(__FILE__) . '/' . $filename, 'rb')) !== false) { 49 | $this->firstip = $this->getlong(); 50 | $this->lastip = $this->getlong(); 51 | $this->totalip = ($this->lastip - $this->firstip) / 7; 52 | } 53 | } 54 | 55 | /** 56 | * 返回读取的长整型数 57 | * 58 | * @access private 59 | * @return int 60 | */ 61 | private function getlong() 62 | { 63 | //将读取的little-endian编码的4个字节转化为长整型数 64 | $result = unpack('Vlong', fread($this->fp, 4)); 65 | 66 | return $result['long']; 67 | } 68 | 69 | /** 70 | * 返回读取的3个字节的长整型数 71 | * 72 | * @access private 73 | * @return int 74 | */ 75 | private function getlong3() 76 | { 77 | //将读取的little-endian编码的3个字节转化为长整型数 78 | $result = unpack('Vlong', fread($this->fp, 3) . chr(0)); 79 | 80 | return $result['long']; 81 | } 82 | 83 | /** 84 | * 返回压缩后可进行比较的IP地址 85 | * 86 | * @access private 87 | * 88 | * @param string $ip 89 | * 90 | * @return string 91 | */ 92 | private function packip($ip) 93 | { 94 | // 将IP地址转化为长整型数,如果在PHP5中,IP地址错误,则返回False, 95 | // 这时intval将Flase转化为整数-1,之后压缩成big-endian编码的字符串 96 | return pack('N', intval(ip2long($ip))); 97 | } 98 | 99 | /** 100 | * 返回读取的字符串 101 | * 102 | * @access private 103 | * 104 | * @param string $data 105 | * 106 | * @return string 107 | */ 108 | private function getstring($data = "") 109 | { 110 | $char = fread($this->fp, 1); 111 | while (ord($char) > 0) { // 字符串按照C格式保存,以\0结束 112 | $data .= $char; // 将读取的字符连接到给定字符串之后 113 | $char = fread($this->fp, 1); 114 | } 115 | 116 | return $data; 117 | } 118 | 119 | /** 120 | * 返回地区信息 121 | * 122 | * @access private 123 | * @return string 124 | */ 125 | private function getarea() 126 | { 127 | $byte = fread($this->fp, 1); // 标志字节 128 | switch (ord($byte)) { 129 | case 0: // 没有区域信息 130 | $area = ""; 131 | break; 132 | case 1: 133 | case 2: // 标志字节为1或2,表示区域信息被重定向 134 | fseek($this->fp, $this->getlong3()); 135 | $area = $this->getstring(); 136 | break; 137 | default: // 否则,表示区域信息没有被重定向 138 | $area = $this->getstring($byte); 139 | break; 140 | } 141 | 142 | return $area; 143 | } 144 | 145 | /** 146 | * 根据所给 IP 地址或域名返回所在地区信息 147 | * 148 | * @access public 149 | * 150 | * @param string $ip 151 | * 152 | * @return array 153 | */ 154 | public function getlocation($ip = '') 155 | { 156 | if (!$this->fp) 157 | return null; // 如果数据文件没有被正确打开,则直接返回空 158 | if (empty($ip)) 159 | $ip = Utils::getClientIp(); 160 | $location['ip'] = gethostbyname($ip); // 将输入的域名转化为IP地址 161 | $ip = $this->packip($location['ip']); // 将输入的IP地址转化为可比较的IP地址 162 | // 不合法的IP地址会被转化为255.255.255.255 163 | // 对分搜索 164 | $l = 0; // 搜索的下边界 165 | $u = $this->totalip; // 搜索的上边界 166 | $findip = $this->lastip; // 如果没有找到就返回最后一条IP记录(QQWry.Dat的版本信息) 167 | while ($l <= $u) { // 当上边界小于下边界时,查找失败 168 | $i = floor(($l + $u) / 2); // 计算近似中间记录 169 | fseek($this->fp, $this->firstip + $i * 7); 170 | $beginip = strrev(fread($this->fp, 4)); // 获取中间记录的开始IP地址 171 | // strrev函数在这里的作用是将little-endian的压缩IP地址转化为big-endian的格式 172 | // 以便用于比较,后面相同。 173 | if ($ip < $beginip) { // 用户的IP小于中间记录的开始IP地址时 174 | $u = $i - 1; // 将搜索的上边界修改为中间记录减一 175 | } else { 176 | fseek($this->fp, $this->getlong3()); 177 | $endip = strrev(fread($this->fp, 4)); // 获取中间记录的结束IP地址 178 | if ($ip > $endip) { // 用户的IP大于中间记录的结束IP地址时 179 | $l = $i + 1; // 将搜索的下边界修改为中间记录加一 180 | } else { // 用户的IP在中间记录的IP范围内时 181 | $findip = $this->firstip + $i * 7; 182 | break; // 则表示找到结果,退出循环 183 | } 184 | } 185 | } 186 | 187 | //获取查找到的IP地理位置信息 188 | fseek($this->fp, $findip); 189 | $location['beginip'] = long2ip($this->getlong()); // 用户IP所在范围的开始地址 190 | $offset = $this->getlong3(); 191 | fseek($this->fp, $offset); 192 | $location['endip'] = long2ip($this->getlong()); // 用户IP所在范围的结束地址 193 | $byte = fread($this->fp, 1); // 标志字节 194 | switch (ord($byte)) { 195 | case 1: // 标志字节为1,表示国家和区域信息都被同时重定向 196 | $countryOffset = $this->getlong3(); // 重定向地址 197 | fseek($this->fp, $countryOffset); 198 | $byte = fread($this->fp, 1); // 标志字节 199 | switch (ord($byte)) { 200 | case 2: // 标志字节为2,表示国家信息又被重定向 201 | fseek($this->fp, $this->getlong3()); 202 | $location['country'] = $this->getstring(); 203 | fseek($this->fp, $countryOffset + 4); 204 | $location['area'] = $this->getarea(); 205 | break; 206 | default: // 否则,表示国家信息没有被重定向 207 | $location['country'] = $this->getstring($byte); 208 | $location['area'] = $this->getarea(); 209 | break; 210 | } 211 | break; 212 | case 2: // 标志字节为2,表示国家信息被重定向 213 | fseek($this->fp, $this->getlong3()); 214 | $location['country'] = $this->getstring(); 215 | fseek($this->fp, $offset + 8); 216 | $location['area'] = $this->getarea(); 217 | break; 218 | default: // 否则,表示国家信息没有被重定向 219 | $location['country'] = $this->getstring($byte); 220 | $location['area'] = $this->getarea(); 221 | break; 222 | } 223 | if (trim($location['country']) == 'CZ88.NET') { // CZ88.NET表示没有有效信息 224 | $location['country'] = '未知'; 225 | } 226 | if (trim($location['area']) == 'CZ88.NET') { 227 | $location['area'] = ''; 228 | } 229 | 230 | return $location; 231 | } 232 | 233 | /** 234 | * 析构函数,用于在页面执行结束后自动关闭打开的文件。 235 | * 236 | */ 237 | public function __destruct() 238 | { 239 | if ($this->fp) { 240 | fclose($this->fp); 241 | } 242 | $this->fp = 0; 243 | } 244 | 245 | } 246 | -------------------------------------------------------------------------------- /libs/PinYin.php: -------------------------------------------------------------------------------- 1 | 160) { 73 | $_Q = ord(substr($_String, ++$i, 1)); 74 | $_P = $_P * 256 + $_Q - 65536; 75 | } 76 | $_Res .= self::_Pinyin($_P, $_Data); 77 | } 78 | 79 | return preg_replace("/[^a-z0-9]*/", '', $_Res); 80 | } 81 | 82 | private static function _Pinyin($_Num, $_Data) 83 | { 84 | if ($_Num > 0 && $_Num < 160) { 85 | return chr($_Num); 86 | } elseif ($_Num < -20319 || $_Num > -10247) { 87 | return ''; 88 | } else { 89 | foreach ($_Data as $k => $v) { 90 | if ($v <= $_Num) 91 | break; 92 | } 93 | 94 | return $k; 95 | } 96 | } 97 | 98 | private static function _U2_Utf8_Gb($_C) 99 | { 100 | $_String = ''; 101 | if ($_C < 0x80) { 102 | $_String .= $_C; 103 | } elseif ($_C < 0x800) { 104 | $_String .= chr(0xC0 | $_C >> 6); 105 | $_String .= chr(0x80 | $_C & 0x3F); 106 | } elseif ($_C < 0x10000) { 107 | $_String .= chr(0xE0 | $_C >> 12); 108 | $_String .= chr(0x80 | $_C >> 6 & 0x3F); 109 | $_String .= chr(0x80 | $_C & 0x3F); 110 | } elseif ($_C < 0x200000) { 111 | $_String .= chr(0xF0 | $_C >> 18); 112 | $_String .= chr(0x80 | $_C >> 12 & 0x3F); 113 | $_String .= chr(0x80 | $_C >> 6 & 0x3F); 114 | $_String .= chr(0x80 | $_C & 0x3F); 115 | } 116 | 117 | return iconv('UTF-8', 'GB2312', $_String); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /libs/UTFWry.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/e-kevin/wocenter/d9112070564b58373e56313ece306d04fc61df92/libs/UTFWry.dat -------------------------------------------------------------------------------- /libs/Utils.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Utils 16 | { 17 | 18 | /** 19 | * 获取客户端IP地址 20 | * 21 | * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 22 | * @param boolean $adv 是否进行高级模式获取(有可能被伪装) 23 | * 24 | * @return mixed 25 | */ 26 | public static function getClientIp($type = 0, $adv = true) 27 | { 28 | $type = $type ? 1 : 0; 29 | static $ip = null; 30 | if ($ip !== null) 31 | return $ip[$type]; 32 | 33 | if ($adv) { 34 | if (isset($_SERVER['HTTP_X_REAL_FORWARDED_FOR'])) { 35 | $ip = $_SERVER['HTTP_X_REAL_FORWARDED_FOR']; 36 | } elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { 37 | $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); 38 | $pos = array_search('unknown', $arr); 39 | if (false !== $pos) 40 | unset($arr[$pos]); 41 | $ip = trim($arr[0]); 42 | } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { 43 | $ip = $_SERVER['HTTP_CLIENT_IP']; 44 | } elseif (isset($_SERVER['REMOTE_ADDR'])) { 45 | $ip = $_SERVER['REMOTE_ADDR']; 46 | } 47 | } elseif (isset($_SERVER['REMOTE_ADDR'])) { 48 | $ip = $_SERVER['REMOTE_ADDR']; 49 | } 50 | // IP地址合法验证 51 | $long = sprintf("%u", ip2long($ip)); 52 | $ip = $long ? [$ip, $long] : ['0.0.0.0', 0]; 53 | 54 | return $ip[$type]; 55 | } 56 | 57 | /** 58 | * 获取客户端IP地址信息 59 | * 60 | * @return mixed 61 | */ 62 | public static function getIpLocation() 63 | { 64 | $last_ip = static::getClientIp(); 65 | $last_location = ''; 66 | if ($last_ip) { //如果获取到客户端IP,则获取其物理位置 67 | $Ip = new IpLocation(); // 实例化类 68 | $location = $Ip->getlocation($last_ip); // 获取某个IP地址所在的位置 69 | 70 | if ($location['country'] && $location['country'] != 'CZ88.NET') 71 | $last_location .= $location['country']; 72 | 73 | if ($location['area'] && $location['area'] != 'CZ88.NET') 74 | $last_location .= ' ' . $location['area']; 75 | } 76 | 77 | return $last_location; 78 | } 79 | 80 | /** 81 | * 格式化字节大小 82 | * 83 | * @param number $size 字节数 84 | * @param string $delimiter 数字和单位分隔符 85 | * 86 | * @return string 格式化后的带单位的大小 87 | */ 88 | public static function formatBytes($size, $delimiter = '') 89 | { 90 | $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']; 91 | for ($i = 0; $size >= 1024 && $i < 5; $i++) 92 | $size /= 1024; 93 | 94 | return round($size, 2) . $delimiter . $units[$i]; 95 | } 96 | 97 | /** 98 | * Example: 99 | * 100 | * ```php 101 | * $query = new \yii\db\Query; 102 | * $query->select('*') 103 | * ->from('{{%post}}') 104 | * ->where('user_id=:user_id', [':user_id' => $user->id]); 105 | * $pages = Utils::Pagination($query); 106 | * $posts = $pages['result']; 107 | * foreach($posts as $post) { 108 | * echo $post['content']; 109 | * } 110 | * echo \yii\widgets\LinkPager::widget([ 111 | * 'pagination' => $pages['pages'], 112 | * ]); 113 | * ``` 114 | * 115 | * @param Query $query SELECT SQL statement 116 | * @param $defaultPageSize 117 | * 118 | * @return array ['result', 'pages'] 119 | */ 120 | public static function pagination($query, $defaultPageSize = 20) 121 | { 122 | $pagination = new Pagination([ 123 | 'totalCount' => $query->count(), 124 | 'defaultPageSize' => $defaultPageSize, 125 | ]); 126 | 127 | $result = $query->offset($pagination->offset) 128 | ->limit($pagination->limit) 129 | ->all(); 130 | 131 | return ['pages' => $pagination, 'result' => $result]; 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /messages/zh-CN/app.php: -------------------------------------------------------------------------------- 1 | 'WC后台管理系统', 4 | 'Focus on digital operation management.' => '专注数字化运营管理', 5 | "We've been doing better for you!" => '我们一直在为您做到更好!', 6 | 'Technical support by {company}' => '技术支持 {company}', 7 | 'Copyright {date} by {company}' => 'Copyright © {date} by {company}. All rights reserved.', 8 | // 操作提示 9 | 'Are you sure you want to perform this operation?' => '确认要执行该操作吗', 10 | 'Select the data to be operated.' => '请选择要操作的数据', 11 | 'Not found.' => '无法查询到相关数据', 12 | 'Page not found.' => '页面未找到', 13 | 'Empty parameters.' => '参数错误:缺少必要的参数~', 14 | 'Empty query.' => '无法查询到结果,请更精确查找!', 15 | 'Unknown error.' => '未知错误', 16 | 'An internal server error occurred.' => '服务器开了个小差,出错啦~', 17 | 'Please delete or move the child data under this data before deleting it.' => '删除该数据前请删除或转移其下的子级数据!', 18 | 'Parent id is invalid.' => '父级ID数据无效!', 19 | 'Login again.' => '重新登录', 20 | 'Return home.' => '返回首页', 21 | 'Jump to url.' => '跳转', 22 | 'Stop to jump.' => '停止跳转', 23 | 'After a few seconds the page will automatically jump.' => '{seconds}秒钟后,页面会自动跳转', 24 | 'Message prompt' => '消息提示', 25 | 'Found a bug? Tell about it to the extension developer.' 26 | => '发现Bug?留言扩展开发者,以便及时修正', 27 | 'Delete confirm.' => '确定要删除所选数据吗?', 28 | 'Delete successful.' => '删除数据成功!', 29 | 'Delete failure.' => '删除数据出错!', 30 | 'Remove confirm.' => '确定要移除信息吗?', 31 | 'Remove successful.' => '成功移除信息!', 32 | 'Remove failure.' => '移除信息出错!', 33 | 'Restore confirm.' => '确定要还原信息吗?', 34 | 'Restore successful.' => '成功还原信息!', 35 | 'Restore failure.' => '还原信息出错!', 36 | 'Operation successful.' => '操作成功!', 37 | 'Operation failure.' => '操作失败!', 38 | 'Create successful.' => '新增成功!', 39 | 'Create failure.' => '新增失败!', 40 | 'Add successful.' => '添加成功!', 41 | 'Add failure.' => '添加失败!', 42 | 'Edit successful.' => '编辑成功!', 43 | 'Edit failure.' => '编辑失败!', 44 | 'Saved successful.' => '保存成功!', 45 | 'Saved failure.' => '保存失败!', 46 | // common 47 | 'Error' => '错误', 48 | 'Success' => '成功', 49 | 'Enable' => '启用', 50 | 'Disable' => '禁用', 51 | 'Yes' => '是', 52 | 'No' => '否', 53 | 'Target self' => '当前窗口', 54 | 'Target blank' => '新窗口', 55 | 'Secrecy' => '保密', 56 | 'Male' => '男', 57 | 'Female' => '女', 58 | 'Hidden' => '隐藏', 59 | 'Display' => '显示', 60 | 'Unlimited' => '不限', 61 | 'Root level' => '顶级菜单', 62 | 'Finished' => '完成', 63 | 'Next Step' => '下一步', 64 | 'Previous Step' => '上一步', 65 | 'Skip' => '跳过', 66 | // 表单 67 | 'Name' => '名称', 68 | 'Status' => '状态', 69 | 'Created At' => '创建时间', 70 | 'Updated At' => '更新时间', 71 | 'Save' => '保存', 72 | 'Reset' => '重置', 73 | 'Search' => '搜索', 74 | 'Close' => '关闭', 75 | 'New add' => '新增', 76 | 'Please choose' => '请选择', 77 | 'Update' => '更新', 78 | 'Delete' => '删除', 79 | 'MultipleDelete' => '批量删除', 80 | 'Deleted' => '已删除', 81 | 'Remove' => '移除', 82 | 'Removed' => '已移除', 83 | 'Restore' => '还原', 84 | 'Install' => '安装', 85 | 'Uninstall' => '卸载', 86 | 'Description' => '描述', 87 | 'Url' => 'URL地址', 88 | 'Url Params' => 'URL参数', 89 | 'Icon' => '图标', 90 | 'Sort Order' => '排序', 91 | // 扩展 92 | 'Create type by user' => '用户', 93 | 'Create type by extension' => '扩展', 94 | // 日期时间 95 | 'Today {time}' => '今天 {time}', 96 | 'Yesterday {time}' => '昨天 {time}', 97 | 'Tomorrow {time}' => '明天 {time}', 98 | 'The day after tomorrow {time}' => '后天 {time}', 99 | 'The day before yesterday {time}' => '前天 {time}', 100 | // DateTimeHelper 101 | 'Hour' => '小时', 102 | 'Minute' => '分钟', 103 | 'Second' => '秒', 104 | 'Year' => '年', 105 | 'Month' => '月', 106 | 'Day' => '日', 107 | 'Week' => '周', 108 | ]; 109 | -------------------------------------------------------------------------------- /models/BaseConfigModel.php: -------------------------------------------------------------------------------- 1 | 30], 38 | [['title'], 'string', 'max' => 50], 39 | [['extra'], 'string', 'max' => 255], 40 | [['remark'], 'string', 'max' => 255], 41 | [['name'], 'unique'], 42 | ]; 43 | } 44 | 45 | /** 46 | * @inheritdoc 47 | */ 48 | public function attributeLabels() 49 | { 50 | return [ 51 | 'id' => 'ID', 52 | 'name' => '标识', 53 | 'type' => '类型', 54 | 'title' => '标题', 55 | 'category_group' => '分组', 56 | 'extra' => '额外数据', 57 | 'remark' => '描述', 58 | 'created_at' => '创建时间', 59 | 'updated_at' => '更新时间', 60 | 'status' => '状态', 61 | 'value' => '默认值', 62 | 'sort_order' => '排序', 63 | 'rule' => '验证规则', 64 | ]; 65 | } 66 | 67 | /** 68 | * @inheritdoc 69 | */ 70 | public function attributeHints() 71 | { 72 | return [ 73 | 'name' => '只能使用英文且不能重复', 74 | 'title' => '用于后台显示的配置标题', 75 | 'sort_order' => '用于分组显示的顺序', 76 | 'type' => '系统会根据不同类型解析配置数据', 77 | 'category_group' => '不分组则不会显示在系统设置中', 78 | 'extra' => '【下拉框、单选框、多选框】类型需要配置该项
多个可用英文符号 , ; 或换行分隔,如:
逗号 ,
key:value, key1:value1, key2:value2' . 79 | '
分号 ;
key:value; key1:value1; key2:value
换行
key:value
key1:value1
key2:value', 80 | 'value' => '默认值', 81 | 'remark' => '配置详细说明', 82 | 'rule' => '配置验证规则
多条规则用英文符号 ; 或换行分隔,如:
分号 ;
required; string,max:10,min:4; string,length:1-3' . 83 | '
换行
required
string,max:10,min:4
string,length:1-3', 84 | ]; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /traits/DispatchShortcutTrait.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | trait DispatchShortcutTrait 11 | { 12 | 13 | use DispatchTrait; 14 | 15 | /** 16 | * 操作失败后返回结果至客户端 17 | * 18 | * @param string|array $message 提示信息 19 | * @param string|array $jumpUrl 页面跳转地址 20 | * @param mixed $data 21 | * - 为整数,则代表页面跳转停留时间,默认为3妙,时间结束后自动跳转至指定的`$jumpUrl`页面 22 | * - 为数组,则代表返回给客户端的数据 23 | * 24 | * @return mixed 25 | */ 26 | public function error($message = '', $jumpUrl = '', $data = []) 27 | { 28 | return $this->getDispatchManager()->getDispatch()->error($message, $jumpUrl, $data); 29 | } 30 | 31 | /** 32 | * 操作成功后返回结果至客户端 33 | * 34 | * @param string|array $message 提示信息 35 | * @param string|array $jumpUrl 页面跳转地址 36 | * @param mixed $data 37 | * - 为整数,则代表页面跳转停留时间,默认为1妙,时间结束后自动跳转至指定的`$jumpUrl`页面 38 | * - 为数组,则代表返回给客户端的数据 39 | * 40 | * @return mixed 41 | */ 42 | public function success($message = '', $jumpUrl = '', $data = []) 43 | { 44 | return $this->getDispatchManager()->getDispatch()->success($message, $jumpUrl, $data); 45 | } 46 | 47 | /** 48 | * 显示页面 49 | * 50 | * @param string $view 51 | * @param array $assign 52 | * 53 | * @return string|\yii\web\Response 54 | */ 55 | public function display($view = null, $assign = []) 56 | { 57 | return $this->getDispatchManager()->getDispatch()->display($view, $assign); 58 | } 59 | 60 | /** 61 | * 保存视图模板文件赋值数据 62 | * 63 | * 示例: 64 | * ```php 65 | * $this->setAssign('name1', 'apple'); 66 | * $this->setAssign('name2', 'orange'); 67 | * 等于 68 | * $this->setAssign([ 69 | * 'name1' => 'apple', 70 | * 'name2' => 'orange' 71 | * ]); 72 | * 73 | * ``` 74 | * 75 | * 使用该方法可向当前控制器的调度器内传入公共模板数据 76 | * 77 | * @param string|array $key 78 | * @param string|array $value 79 | * 80 | * @return self 81 | */ 82 | public function assign($key, $value = null) 83 | { 84 | $this->getDispatchManager()->getDispatch()->assign($key, $value); 85 | 86 | return $this; 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /traits/DispatchTrait.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | trait DispatchTrait 21 | { 22 | 23 | /** 24 | * @var Modularity 25 | */ 26 | public $module; 27 | 28 | /** 29 | * @var Dispatch 30 | */ 31 | public $action; 32 | 33 | /** 34 | * @var int 运行模式 35 | * @see DispatchManager::$runMode // fixme 36 | */ 37 | public $runMode; 38 | 39 | /** 40 | * @return array 调度器配置 41 | * @see DispatchManagerInterface::getDispatchMap() 42 | */ 43 | public $dispatchMap; 44 | 45 | /** 46 | * @var array 默认调度器配置 47 | * @see DispatchManager::$_defaultDispatches // fixme 48 | */ 49 | protected $defaultDispatches = []; 50 | 51 | /** 52 | * @inheritdoc 53 | * 54 | * @param string $id 55 | * 56 | * @return null|Action|Dispatch 57 | */ 58 | public function createAction($id) 59 | { 60 | return parent::createAction($id) ?: $this->getDispatchManager()->createDispatch($id); 61 | } 62 | 63 | /** 64 | * @var DispatchManagerInterface 65 | */ 66 | private $_dispatchManager; 67 | 68 | /** 69 | * 获取调度管理器 70 | * 71 | * @return object|DispatchManagerInterface 72 | */ 73 | public function getDispatchManager() 74 | { 75 | if (null === $this->_dispatchManager) { 76 | $this->_dispatchManager = Wc::getDispatchManager($this, $this->defaultDispatches); 77 | } 78 | 79 | return $this->_dispatchManager; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /traits/ExtendModelTrait.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | trait ExtendModelTrait 18 | { 19 | 20 | /** 21 | * 抛出异常 22 | * 23 | * @var boolean 24 | */ 25 | protected $_throwException; 26 | 27 | /** 28 | * @inheritdoc 29 | */ 30 | public function behaviors() 31 | { 32 | return array_merge(parent::behaviors(), [ 33 | CatchMessageBehavior::class, 34 | ]); 35 | } 36 | 37 | /** 38 | * 获取是否允许抛出异常 39 | * 40 | * @return boolean 41 | */ 42 | public function getThrowException() 43 | { 44 | return $this->_throwException; 45 | } 46 | 47 | /** 48 | * 设置是否允许抛出异常,默认允许抛出异常 49 | * 50 | * @param boolean $throw 51 | * 52 | * @return $this 53 | */ 54 | public function setThrowException($throw = true) 55 | { 56 | $this->_throwException = $throw; 57 | 58 | return $this; 59 | } 60 | 61 | /** 62 | * 获取模型所有数据,通常结合缓存使用 63 | * 64 | * @return array 65 | */ 66 | public function getAll() 67 | { 68 | return []; 69 | } 70 | 71 | /** 72 | * 清除缓存 73 | */ 74 | public function clearCache() 75 | { 76 | } 77 | 78 | /** 79 | * @var integer|false 缓存时间间隔 80 | */ 81 | private $_cacheDuration; 82 | 83 | /** 84 | * 获取缓存时间间隔 85 | * 86 | * @return false|int 87 | */ 88 | public function getCacheDuration() 89 | { 90 | if (null === $this->_cacheDuration) { 91 | $this->setCacheDuration(); 92 | } 93 | 94 | return $this->_cacheDuration; 95 | } 96 | 97 | /** 98 | * 设置缓存时间间隔 99 | * 100 | * @param false|int $cacheDuration 当为`false`时,则删除缓存数据,默认缓存`一天` 101 | * 102 | * @return $this 103 | */ 104 | public function setCacheDuration($cacheDuration = 86400) 105 | { 106 | $this->_cacheDuration = $cacheDuration; 107 | 108 | return $this; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /traits/ExtensionTrait.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | trait ExtensionTrait 18 | { 19 | 20 | /** 21 | * @var string|Connection DB连接对象或DB连接的应用程序组件ID,主要是为扩展提供操作数据库功能 22 | */ 23 | public $db = 'db'; 24 | 25 | /** 26 | * @return Connection 27 | */ 28 | public function getDb() 29 | { 30 | if (!Yii::$app->has($this->db,true)) { 31 | $this->db = Yii::$app->get($this->db); 32 | $this->db->getSchema()->refresh(); 33 | $this->db->enableSlaves = false; 34 | } 35 | 36 | return $this->db; 37 | } 38 | 39 | /** 40 | * 执行migrate操作 41 | * 42 | * @param string $type 操作类型 43 | */ 44 | protected function runMigrate($type) 45 | { 46 | if (FileHelper::isDir(Yii::getAlias($this->migrationPath))) { 47 | $action = "migrate/"; 48 | switch ($type) { 49 | case 'up': 50 | $action .= 'up'; 51 | break; 52 | case 'down': 53 | $action .= 'down'; 54 | break; 55 | default: 56 | throw new InvalidArgumentException('The "type" property is invalid.'); 57 | } 58 | $cmd = "%s {$action} --migrationPath=%s --interactive=0 all"; 59 | //执行 60 | ConsoleHelper::run(sprintf($cmd, 61 | Yii::getAlias(ConsoleHelper::getCommander()), 62 | Yii::getAlias($this->migrationPath) 63 | ), false); 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /traits/LoadModelTrait.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | trait LoadModelTrait 15 | { 16 | 17 | /** 18 | * 根据模型主键获取相关数据,如果数据不存在,则抛出404异常 19 | * 20 | * @param \yii\db\ActiveRecord|string $modelClass 模型对象 21 | * @param integer $id 主键ID 22 | * @param boolean $throwException 是否允许抛出异常 23 | * @param array $config 配置模型属性 e.g. ['scenario' => 'update'] 24 | * 25 | * @return null|\yii\db\ActiveRecord 26 | * @throws InvalidConfigException 27 | * @throws NotFoundHttpException if the model cannot be found 28 | */ 29 | protected function loadModel($modelClass, $id, $throwException = true, $config = []) 30 | { 31 | if (!class_exists($modelClass)) { 32 | throw new InvalidConfigException("Model Class `{$modelClass}` does not exists"); 33 | } 34 | $model = $modelClass::findOne($id); 35 | if (!is_object($model)) { 36 | if ($throwException) { 37 | throw new NotFoundHttpException('The requested page does not exist.'); 38 | } else { 39 | return null; 40 | } 41 | } 42 | if (!empty($config)) { 43 | Yii::configure($model, $config); 44 | } 45 | 46 | return $model; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /traits/ParseRulesTrait.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | trait ParseRulesTrait 11 | { 12 | 13 | /** 14 | * 解析验证规则 15 | * 16 | * @param string $rules 验证规则 e.g. required;string,max:30;string,length:1-3 17 | * @param string $field 需要验证的字段 18 | * 19 | * @return array e.g. 20 | * [ 21 | * [$field, 'required'], 22 | * [$field, 'string', 'max' => 30], 23 | * [$field, 'string', 'length' => [1,3]] 24 | * ] 25 | */ 26 | protected function parseRulesToArray($rules, $field) 27 | { 28 | $value = []; 29 | if (!empty($rules)) { 30 | // 获取所有规则 31 | $array = preg_split('/[;\r\n]+/', trim($rules, ";\r\n")); 32 | if (!empty($array)) { 33 | foreach ($array as $key => $val) { 34 | $value[$key][] = $field; // [$field] 35 | $tmp = explode(',', $val); 36 | $value[$key][] = $tmp[0]; // [$field, 'required']|[$field, 'string'] 37 | array_shift($tmp); // $tmp = max:30|length:1-3 38 | foreach ($tmp as $k => $v) { 39 | if (strpos($v, ':')) { 40 | $t = explode(':', $v); 41 | if (strpos($t[1], '-')) { 42 | $value[$key][$t[0]] = explode('-', $t[1]); // [$field, 'string', 'length' => [1,3]] 43 | } else { 44 | $value[$key][$t[0]] = intval($t[1]); // [$field, 'string', 'max' => 30] 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | return $value; 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /validators/DateControlValidator.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class DateControlValidator extends NumberValidator 16 | { 17 | /** 18 | * @var integer 日期上限,默认为`null`,表示没有上限。当为整数时,表示该值是一个时间戳。 19 | */ 20 | public $max; 21 | /** 22 | * @var integer 日期下限,默认为`null`,表示没有下限。当为整数时,表示该值是一个时间戳。 23 | * 24 | * '1901-12-14 04:45:52'的时间戳为 -2147483648,mysql int类型保存的范围从 -2^31 (-2,147,483,648) 到 2^31 – 1 (2,147,483,647) 25 | * 的整型数据(所有数字)。存储大小为 4 个字节。 26 | */ 27 | public $min = -2147483648; 28 | /** 29 | * @var string 当被验证的值大于[[max]]时,将会显示该条用户定义的错误信息 30 | */ 31 | public $tooBig; 32 | /** 33 | * @var string 当被验证的值大于[[min]]时,将会显示该条用户定义的错误信息 34 | */ 35 | public $tooSmall; 36 | /** 37 | * @var string 当错误信息显示时,该值将被用于显示错误的上限信息 38 | * 当该值为`null`时,将被赋予[[max]]值 39 | */ 40 | public $maxString; 41 | /** 42 | * @var string 当错误信息显示时,该值将被用于显示错误的下限信息 43 | * 当该值为`null`时,将被赋予[[min]]值 44 | */ 45 | public $minString = '1901-12-14 04:45:52'; 46 | 47 | /** 48 | * @inheritdoc 49 | */ 50 | public function init() 51 | { 52 | parent::init(); 53 | 54 | $this->integerOnly = true; // 时间戳必须是整数型 55 | if ($this->maxString === null) { 56 | $this->maxString = (string)$this->max; 57 | } 58 | if ($this->minString === null) { 59 | $this->minString = (string)$this->min; 60 | } 61 | } 62 | 63 | /** 64 | * @inheritdoc 65 | */ 66 | public function validateAttribute($model, $attribute) 67 | { 68 | $timestamp = $model->$attribute; 69 | if (is_array($timestamp) || (is_object($timestamp) && !method_exists($timestamp, '__toString'))) { 70 | $this->addError($model, $attribute, $this->message); 71 | 72 | return; 73 | } elseif (!preg_match($this->integerPattern, StringHelper::normalizeNumber($timestamp))) { 74 | $this->addError($model, $attribute, $this->message); 75 | } elseif ($this->min !== null && $timestamp < $this->min) { 76 | $this->addError($model, $attribute, $this->tooSmall, ['min' => $this->minString]); 77 | } elseif ($this->max !== null && $timestamp > $this->max) { 78 | $this->addError($model, $attribute, $this->tooBig, ['max' => $this->maxString]); 79 | } 80 | } 81 | 82 | /** 83 | * @inheritdoc 84 | */ 85 | protected function validateValue($value) 86 | { 87 | if (is_array($value) || is_object($value)) { 88 | return [Yii::t('yii', '{attribute} is invalid.'), []]; 89 | } 90 | if (!preg_match($this->integerPattern, StringHelper::normalizeNumber($value))) { 91 | return [$this->message, []]; 92 | } elseif ($this->min !== null && $value < $this->min) { 93 | return [$this->tooSmall, ['min' => $this->minString]]; 94 | } elseif ($this->max !== null && $value > $this->max) { 95 | return [$this->tooBig, ['max' => $this->maxString]]; 96 | } else { 97 | return null; 98 | } 99 | } 100 | 101 | /** 102 | * @inheritdoc 103 | */ 104 | public function getClientOptions($model, $attribute) 105 | { 106 | $label = $model->getAttributeLabel($attribute); 107 | 108 | $options = [ 109 | 'pattern' => new JsExpression($this->integerOnly ? $this->integerPattern : $this->numberPattern), 110 | 'message' => Yii::$app->getI18n()->format($this->message, [ 111 | 'attribute' => $label, 112 | ], Yii::$app->language), 113 | ]; 114 | 115 | if ($this->min !== null) { 116 | // ensure numeric value to make javascript comparison equal to PHP comparison 117 | // https://github.com/yiisoft/yii2/issues/3118 118 | $options['min'] = is_string($this->min) ? (float)$this->min : $this->min; 119 | $options['tooSmall'] = Yii::$app->getI18n()->format($this->tooSmall, [ 120 | 'attribute' => $label, 121 | 'min' => $this->minString, 122 | ], Yii::$app->language); 123 | } 124 | if ($this->max !== null) { 125 | // ensure numeric value to make javascript comparison equal to PHP comparison 126 | // https://github.com/yiisoft/yii2/issues/3118 127 | $options['max'] = is_string($this->max) ? (float)$this->max : $this->max; 128 | $options['tooBig'] = Yii::$app->getI18n()->format($this->tooBig, [ 129 | 'attribute' => $label, 130 | 'max' => $this->maxString, 131 | ], Yii::$app->language); 132 | } 133 | if ($this->skipOnEmpty) { 134 | $options['skipOnEmpty'] = 1; 135 | } 136 | 137 | return $options; 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /views/_issue-message.php: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 |
13 |
14 |

15 | 16 | $issueUrl] 19 | ) ?> 20 |

21 |
22 |
23 | 24 | -------------------------------------------------------------------------------- /views/template.php: -------------------------------------------------------------------------------- 1 | 6 | use wocenter\db\Migration; 7 | 8 | class extends Migration 9 | { 10 | 11 | public function safeUp() 12 | { 13 | } 14 | 15 | public function safeDown() 16 | { 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /widgets/DateControl.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class DateControl extends baseDateControl 14 | { 15 | 16 | public function init() 17 | { 18 | $this->initConfigByBehavior(); 19 | 20 | parent::init(); 21 | } 22 | 23 | /** 24 | * 如果存在模型且模型拥有\wocenter\behaviors\ModifyTimestampBehavior行为,则根据行为配置来初始化一些相关参数 25 | */ 26 | protected function initConfigByBehavior() 27 | { 28 | if ($this->hasModel() && $this->model->hasProperty('dateTimeWidget')) { 29 | /** @var ModifyTimestampBehavior $modifyTimestampBehavior */ 30 | $modifyTimestampBehavior = $this->model; 31 | $displayData = $modifyTimestampBehavior->getDisplayFormat($this->attribute, true); 32 | $saveData = $modifyTimestampBehavior->getSaveFormat($this->attribute, true); 33 | $this->type = $displayData['type']; 34 | $this->displayFormat = $displayData['format']; 35 | $this->saveFormat = $saveData['format']; 36 | $this->displayTimezone = $modifyTimestampBehavior->displayTimeZone; 37 | $this->saveTimezone = $modifyTimestampBehavior->saveTimeZone; 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /widgets/DateTimePicker.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class DateTimePicker extends kvDateTimePicker 14 | { 15 | 16 | /** 17 | * The date format, combination of p, P, h, hh, i, ii, s, ss, d, dd, m, mm, M, MM, yy, yyyy. 18 | * - p : meridian in lower case ('am' or 'pm') - according to locale file 19 | * - P : meridian in upper case ('AM' or 'PM') - according to locale file 20 | * - s : seconds without leading zeros 21 | * - ss : seconds, 2 digits with leading zeros 22 | * - i : minutes without leading zeros 23 | * - ii : minutes, 2 digits with leading zeros 24 | * - h : hour without leading zeros - 24-hour format 25 | * - hh : hour, 2 digits with leading zeros - 24-hour format 26 | * - H : hour without leading zeros - 12-hour format 27 | * - HH : hour, 2 digits with leading zeros - 12-hour format 28 | * - d : day of the month without leading zeros 29 | * - dd : day of the month, 2 digits with leading zeros 30 | * - m : numeric representation of month without leading zeros 31 | * - mm : numeric representation of the month, 2 digits with leading zeros 32 | * - M : short textual representation of a month, three letters 33 | * - MM : full textual representation of a month, such as January or March 34 | * - yy : two digit representation of a year 35 | * 36 | * @var string 37 | */ 38 | public $format = 'Y-m-d H:i:s'; 39 | 40 | /** 41 | * weekStart 42 | * Day of the week start. 0 (Sunday) to 6 (Saturday) 43 | * 44 | * @var integer 45 | */ 46 | public $weekStart = 0; 47 | 48 | /** 49 | * The earliest date that may be selected; all earlier dates will be disabled. 50 | * '1901-12-14 04:45:52'的时间戳为 -2147483648,mysql int类型保存的范围从 -2^31 (-2,147,483,648) 到 2^31 – 1 (2,147,483,647) 51 | * 的整型数据(所有数字)。存储大小为 4 个字节。 52 | * 53 | * @var string 54 | */ 55 | public $startDate = '1901-12-14 04:45:52'; 56 | 57 | /** 58 | * The latest date that may be selected; all later dates will be disabled. 59 | * 60 | * @var string 61 | */ 62 | public $endDate = null; 63 | 64 | /** 65 | * Days of the week that should be disabled. Values are 0 (Sunday) to 6 (Saturday). Multiple values should be 66 | * comma-separated. Example: disable weekends: '0,6' or [0,6]. 67 | * 68 | * @var string|array 69 | */ 70 | public $daysOfWeekDisabled = []; 71 | 72 | /** 73 | * Whether or not to close the datetimepicker immediately when a date is selected. 74 | * 75 | * @var boolean 76 | */ 77 | public $autoclose = true; 78 | 79 | /** 80 | * The view that the datetimepicker should show when it is opened. Accepts values of : 81 | * - 0 or 'hour' for the hour view 82 | * - 1 or 'day' for the day view 83 | * - 2 or 'month' for month view (the default) 84 | * - 3 or 'year' for the 12-month overview 85 | * - 4 or 'decade' for the 10-year overview. Useful for date-of-birth datetimepickers. 86 | * 87 | * @var string|integer 88 | */ 89 | public $startView = 2; 90 | 91 | /** 92 | * The lowest view that the datetimepicker should show. 93 | * - 0 or 'hour' for the hour view 94 | * - 1 or 'day' for the day view 95 | * - 2 or 'month' for month view (the default) 96 | * - 3 or 'year' for the 12-month overview 97 | * - 4 or 'decade' for the 10-year overview. Useful for date-of-birth datetimepickers. 98 | * 99 | * @var string|integer 100 | */ 101 | public $minView = 0; 102 | 103 | /** 104 | * The highest view that the datetimepicker should show. 105 | * - 0 or 'hour' for the hour view 106 | * - 1 or 'day' for the day view 107 | * - 2 or 'month' for month view (the default) 108 | * - 3 or 'year' for the 12-month overview 109 | * - 4 or 'decade' for the 10-year overview. Useful for date-of-birth datetimepickers. 110 | * 111 | * @var string|integer 112 | */ 113 | public $maxView = 4; 114 | 115 | /** 116 | * If true or "linked", displays a "Today" button at the bottom of the datetimepicker to select the current date. 117 | * If true, the "Today" button will only move the current date into view; 118 | * If "linked", the current date will also be selected. 119 | * 120 | * @var boolean|string 121 | */ 122 | public $todayBtn = true; 123 | 124 | /** 125 | * If true, highlights the current date. 126 | * 127 | * @var boolean 128 | */ 129 | public $todayHighlight = true; 130 | 131 | /** 132 | * Whether or not to allow date navigation by arrow keys. 133 | * 134 | * @var boolean 135 | */ 136 | public $keyboardNavigation = true; 137 | 138 | /** 139 | * Whether or not to force parsing of the input value when the picker is closed. 140 | * That is, when an invalid date is left in the input field by the user, the picker will forcibly parse that value, 141 | * and set the input's value to the new, valid date, conforming to the given format. 142 | * 143 | * @var boolean 144 | */ 145 | public $forceParse = true; 146 | 147 | public $showMeridian = false; 148 | 149 | /** 150 | * The increment used to build the hour view. A preset is created for each minuteStep minutes. 151 | * 152 | * @var integer 153 | */ 154 | public $minuteStep = 5; 155 | 156 | public $pluginOptions = []; 157 | 158 | public function init() 159 | { 160 | $this->pluginOptions = ArrayHelper::merge([ 161 | 'format' => $this->convertDateFormat($this->format), 162 | 'weekStart' => $this->weekStart, 163 | 'startDate' => $this->startDate, 164 | 'endDate' => $this->endDate, 165 | 'daysOfWeekDisabled' => $this->daysOfWeekDisabled, 166 | 'autoclose' => $this->autoclose, 167 | 'startView' => $this->startView, 168 | 'minView' => $this->minView, 169 | 'maxView' => $this->maxView, 170 | 'todayBtn' => $this->todayBtn, 171 | 'todayHighlight' => $this->todayHighlight, 172 | 'keyboardNavigation' => $this->keyboardNavigation, 173 | 'forceParse' => $this->forceParse, 174 | 'showMeridian' => $this->showMeridian, 175 | 'minuteStep' => $this->minuteStep, 176 | ], $this->pluginOptions); 177 | 178 | parent::init(); 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /widgets/FlashAlert.php: -------------------------------------------------------------------------------- 1 | session->getAllFlashes()处收集信息并以 \yii\bootstrap\Alert 组件显示出来 13 | * 14 | * ```php 15 | * Yii::$app->session->setFlash('error', 'This is the message'); 16 | * Yii::$app->session->setFlash('success', 'This is the message'); 17 | * Yii::$app->session->setFlash('info', 'This is the message'); 18 | * ``` 19 | * 20 | * 支持数组信息 21 | * 22 | * ```php 23 | * Yii::$app->session->setFlash('error', ['Error 1', 'Error 2']); 24 | * ``` 25 | * todo 允许自定义header、icon 26 | * 27 | * @author E-Kevin 28 | */ 29 | class FlashAlert extends Widget 30 | { 31 | 32 | /** 33 | * @var array the HTML attributes for the widget container tag. 34 | * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. 35 | */ 36 | public $options = []; 37 | 38 | /** 39 | * @var boolean 是否显示头部标题,默认不显示 40 | */ 41 | public $showHeader = false; 42 | 43 | /** 44 | * @var array 提醒类型定义 45 | */ 46 | protected $alertTypes = [ 47 | 'error' => [ 48 | 'class' => 'alert-danger', 49 | 'header' => 'Error!', 50 | 'icon' => 'ban', 51 | ], 52 | 'danger' => [ 53 | 'class' => 'alert-danger', 54 | 'header' => 'Danger!', 55 | 'icon' => 'bug', 56 | ], 57 | 'success' => [ 58 | 'class' => 'alert-success', 59 | 'header' => 'Success!', 60 | 'icon' => 'check', 61 | ], 62 | 'info' => [ 63 | 'class' => 'alert-info', 64 | 'header' => 'Info!', 65 | 'icon' => 'info-circle', 66 | ], 67 | 'warning' => [ 68 | 'class' => 'alert-warning', 69 | 'header' => 'Warning!', 70 | 'icon' => 'warning', 71 | ], 72 | ]; 73 | 74 | /** 75 | * @var array the options for rendering the close button tag. 76 | * The close button is displayed in the header of the modal window. Clicking 77 | * on the button will hide the modal window. If this is false, no close button will be rendered. 78 | * 79 | * The following special options are supported: 80 | * 81 | * - tag: string, the tag name of the button. Defaults to 'button'. 82 | * - label: string, the label of the button. Defaults to '×'. 83 | * 84 | * The rest of the options will be rendered as the HTML attributes of the button tag. 85 | * Please refer to the [Alert documentation](http://getbootstrap.com/components/#alerts) 86 | * for the supported HTML attributes. 87 | */ 88 | public $closeButton = []; 89 | 90 | public function init() 91 | { 92 | parent::init(); 93 | 94 | $session = Yii::$app->session; 95 | $flashes = $session->getAllFlashes(); 96 | $appendCss = isset($this->options['class']) ? ' ' . $this->options['class'] : ''; 97 | 98 | foreach ($flashes as $type => $data) { 99 | if (isset($this->alertTypes[$type])) { 100 | $alterTypes = $this->alertTypes[$type]; 101 | $header = $this->showHeader ? Html::tag('h4', (isset($alterTypes['icon']) ? new Icon($alterTypes['icon']) . ' ' : '') . $alterTypes['header']) : ''; 102 | $data = (array) $data; 103 | // 支持数组格式的信息提醒 104 | foreach ($data as $i => $message) { 105 | $this->options['class'] = $alterTypes['class'] . $appendCss; 106 | $this->options['id'] = $type . '-' . $this->getId() . '-' . $i; 107 | 108 | echo Alert::widget([ 109 | 'body' => $header . $message, 110 | 'closeButton' => $this->closeButton, 111 | 'options' => $this->options, 112 | ]); 113 | } 114 | 115 | $session->removeFlash($type); 116 | } 117 | } 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /widgets/Issue.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Issue extends Widget 16 | { 17 | 18 | public $context; 19 | 20 | public function init() 21 | { 22 | parent::init(); 23 | 24 | /** @var RunningExtensionInterface $runningExtension */ 25 | $runningExtension = Wc::getRunningExtension($this->context ?? Yii::$app->controller); 26 | $issueUrl = $runningExtension->getInfo()->getIssueUrl() ?: $runningExtension->defaultExtension()->getInfo()->getIssueUrl(); 27 | 28 | echo $this->render('@wocenter/views/_issue-message', [ 29 | 'issueUrl' => $issueUrl, 30 | ]); 31 | } 32 | 33 | } 34 | --------------------------------------------------------------------------------