├── .bowerrc ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── assets ├── AdminLteAsset.php ├── AppAsset.php ├── ArtTemplateAsset.php ├── BootstrapGrowlAsset.php ├── PageDownAsset.php └── SelectizeAsset.php ├── behaviors └── TimeFormatBehavior.php ├── codeception.yml ├── commands └── HelloController.php ├── components ├── AssetBundle.php ├── ComposerInstaller.php ├── Config.php ├── Controller.php ├── DbAuthManager.php └── db │ ├── ActiveRecord.php │ ├── Command.php │ ├── Connection.php │ └── Migration.php ├── composer.json ├── config ├── assets.php ├── console.php ├── db-default.php ├── modules.php ├── params.php ├── rewrite.php └── web.php ├── controllers ├── SiteController.php └── TagController.php ├── helpers ├── Console.php └── StaticHelper.php ├── mail └── layouts │ └── html.php ├── migrations ├── data │ ├── Data ERD.mwb │ └── EERD.svg ├── m140910_000000_initTable.php ├── m140910_000001_initRbacTable.php ├── m140910_100100_initAdminTable.php ├── m140910_100200_initUserTable.php └── m140910_100300_initForumTable.php ├── models ├── Config.php ├── ContactForm.php ├── Storage.php ├── Tag.php ├── TagItem.php ├── TagQuery.php └── TagSearch.php ├── modules ├── admin │ ├── Module.php │ ├── assets │ │ ├── AdminAsset.php │ │ └── web │ │ │ ├── css │ │ │ └── admin.css │ │ │ └── js │ │ │ └── admin.js │ ├── components │ │ └── Controller.php │ ├── controllers │ │ ├── DefaultController.php │ │ └── UserController.php │ ├── helpers │ │ └── AdminHelper.php │ ├── models │ │ ├── AuthChildItemForm.php │ │ └── AuthItemForm.php │ ├── views │ │ ├── default │ │ │ └── index.php │ │ ├── layouts │ │ │ └── main.php │ │ └── user │ │ │ └── index.php │ └── widgets │ │ ├── ActiveForm.php │ │ └── GridView.php ├── forum │ ├── Module.php │ ├── assets │ │ ├── ForumAsset.php │ │ └── web │ │ │ ├── css │ │ │ └── forum.css │ │ │ └── images │ │ │ └── wmd-buttons.png │ ├── controllers │ │ ├── DefaultController.php │ │ └── TopicController.php │ ├── models │ │ ├── Comment.php │ │ ├── CommentQuery.php │ │ ├── CommentSearch.php │ │ ├── Forum.php │ │ ├── ForumSearch.php │ │ ├── Topic.php │ │ ├── TopicQuery.php │ │ ├── TopicSearch.php │ │ └── TopicTrait.php │ └── views │ │ ├── default │ │ ├── _form.php │ │ ├── _search.php │ │ ├── _topic.php │ │ ├── _topicForm.php │ │ ├── create.php │ │ ├── index.php │ │ ├── post.php │ │ ├── update.php │ │ └── view.php │ │ └── topic │ │ ├── _commentForm.php │ │ ├── _search.php │ │ ├── _topic.php │ │ ├── test.php │ │ └── view.php └── user │ ├── Module.php │ ├── assets │ ├── UserAsset.php │ └── web │ │ └── css │ │ └── user.css │ ├── components │ ├── User.php │ └── authclient │ │ └── clients │ │ ├── QQ.php │ │ └── WeiBo.php │ ├── controllers │ ├── DefaultController.php │ ├── HomeController.php │ └── admin │ │ ├── RbacController.php │ │ └── UserController.php │ ├── helpers │ ├── UserAvatarHelper.php │ └── UserHelper.php │ ├── models │ ├── Avatar.php │ ├── Favorite.php │ ├── Hate.php │ ├── Like.php │ ├── LoginForm.php │ ├── Meta.php │ ├── MetaTrait.php │ ├── Profile.php │ ├── RegisterForm.php │ ├── User.php │ ├── UserAdmin.php │ └── UserAvatar.php │ └── views │ ├── admin │ ├── rbac │ │ ├── index.php │ │ ├── permissions.php │ │ ├── roles.php │ │ └── update.php │ └── user │ │ ├── _form.php │ │ ├── _search.php │ │ ├── create.php │ │ ├── index.php │ │ ├── update.php │ │ └── view.php │ ├── default │ ├── index.php │ └── login.php │ └── home │ └── index.php ├── requirements.php ├── runtime └── .gitignore ├── tests ├── .gitignore ├── README.md ├── _bootstrap.php ├── _config.php ├── _console_bootstrap.php ├── _data │ └── dump.sql ├── _helpers │ ├── CodeHelper.php │ ├── TestHelper.php │ └── WebHelper.php ├── _log │ └── .gitignore ├── _pages │ ├── AboutPage.php │ ├── ContactPage.php │ └── LoginPage.php ├── acceptance.suite.yml ├── acceptance │ ├── AboutCept.php │ ├── ContactCept.php │ ├── HomeCept.php │ ├── LoginCept.php │ ├── _bootstrap.php │ ├── _config.php │ ├── _console.php │ ├── yii │ └── yii.bat ├── functional.suite.yml ├── functional │ ├── AboutCept.php │ ├── ContactCept.php │ ├── HomeCept.php │ ├── LoginCept.php │ ├── _bootstrap.php │ ├── _config.php │ ├── _console.php │ ├── yii │ └── yii.bat ├── unit.suite.yml └── unit │ ├── _bootstrap.php │ ├── _config.php │ ├── _console.php │ ├── fixtures │ └── .gitkeep │ ├── models │ ├── ContactFormTest.php │ ├── LoginFormTest.php │ └── UserTest.php │ ├── templates │ └── fixtures │ │ └── .gitkeep │ ├── yii │ └── yii.bat ├── vendor └── .gitignore ├── views ├── common │ └── message.php ├── layouts │ └── main.php ├── site │ ├── about.php │ ├── contact.php │ ├── error.php │ ├── index.php │ ├── login.php │ └── test.php └── tag │ ├── _form.php │ ├── _search.php │ ├── create.php │ ├── index.php │ ├── update.php │ └── view.php ├── web ├── .htaccess ├── assets │ └── .gitignore ├── css │ ├── global.css │ ├── index.css │ └── ui.css ├── favicon.ico ├── images │ └── anonymous.jpg ├── index-test.php ├── index.php ├── js │ ├── global.js │ └── index.js ├── nginx.htaccess └── robots.txt ├── widgets ├── AjaxModal.php ├── Alert.php ├── TopMenu.php └── views │ ├── ajaxModal.php │ └── topMenu.php ├── yii └── yii.bat /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory" : "vendor/bower" 3 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | # Autodetect text files 3 | * text=auto 4 | 5 | # ...Unless the name matches the following overriding patterns 6 | 7 | # Definitively text files 8 | *.php text 9 | *.css text 10 | *.js text 11 | *.txt text 12 | *.md text 13 | *.xml text 14 | *.json text 15 | *.bat text 16 | *.sql text 17 | *.xml text 18 | *.yml text 19 | 20 | # Ensure those won't be messed up with 21 | *.png binary 22 | *.jpg binary 23 | *.gif binary 24 | *.ttf binary -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 保留所有 .gitignore 文件 2 | !.gitignore 3 | 4 | # 数据库文件 5 | /config/db.php 6 | 7 | # phpstorm 项目文件 8 | .idea 9 | 10 | # netbeans 项目文件 11 | nbproject 12 | 13 | # zend studio for eclipse 项目文件 14 | .buildpath 15 | .project 16 | .settings 17 | 18 | # windows 缩略图换成 19 | Thumbs.db 20 | 21 | # composer 供应商目录 22 | /vendor 23 | 24 | # composer 它本身是不需要的(客户端自行升级最新版) 25 | composer.phar 26 | # composer.lock 我们想要最新版的类库,因此无需提交此文件 27 | /composer.lock 28 | 29 | # Mac DS_Store 文件 30 | .DS_Store 31 | 32 | # phpunit 它本身是不需要的 33 | phpunit.phar 34 | # 本地 phpunit 配置文件 35 | /phpunit.xml 36 | 37 | # 自动生成的文件无需提交 38 | /runtime 39 | /web/assets 40 | /modules/admin/assets 41 | /modules/forum/assets 42 | /modules/user/assets -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Yii2-中文化 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of yii-QA nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | Copyright (c) 2014 著作权由Yii2 中文化所有。著作权人保留一切权利。 30 | 31 | 这份授权条款,在使用者符合以下三条件的情形下,授予使用者使用及再散播本 32 | 软件包装原始码及二进位可执行形式的权利,无论此包装是否经改作皆然: 33 | 34 | * 对于本软件源代码的再散播,必须保留上述的版权宣告、此三条件表列,以 35 | 及下述的免责声明。 36 | * 对于本套件二进位可执行形式的再散播,必须连带以文件以及/或者其他附 37 | 于散播包装中的媒介方式,重制上述之版权宣告、此三条件表列,以及下述 38 | 的免责声明。 39 | * 未获事前取得书面许可,不得使用柏克莱加州大学或本软件贡献者之名称, 40 | 来为本软件之衍生物做任何表示支持、认可或推广、促销之行为。 41 | 42 | 免责声明:本软件是由 Yii2 中文化及本软件之贡献者以现状("as is")提供, 43 | 本软件包装不负任何明示或默示之担保责任,包括但不限于就适售性以及特定目 44 | 的的适用性为默示性担保。Yii2 中文化及本软件之贡献者,无论任何条件、 45 | 无论成因或任何责任主义、无论此责任为因合约关系、无过失责任主义或因非违 46 | 约之侵权(包括过失或其他原因等)而起,对于任何因使用本软件包装所产生的 47 | 任何直接性、间接性、偶发性、特殊性、惩罚性或任何结果的损害(包括但不限 48 | 于替代商品或劳务之购用、使用损失、资料损失、利益损失、业务中断等等), 49 | 不负任何责任,即在该种使用已获事前告知可能会造成此类损害的情形下亦然。 50 | 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Yii-QA 简介(此项目目前已不再维护) 2 | =========== 3 | 4 | 感谢选择 Yii-QA,基于 [Yii2](https://github.com/yiisoft/yii2) 框架基础实现的问答程序。 5 | 6 | 7 | 8 | 9 | 10 | 11 | #鉴于目前的业余时间有限,无法管理太多的业余项目, 我准备合并目前手上的项目, 集成在一个项目中, 感谢支持!!!!!!! 12 | 13 | 请关注: [画卷](https://github.com/callmez/huajuan) 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 环境条件 22 | ------- 23 | 简而言之: 24 | 25 | - >= php5.4 26 | 27 | >提示:详细的信息请在参考下个板块安装后,检查`http://localhost//requirements.php` 页面了解详情。 28 | 29 | 通过 Composer 安装 30 | ---------------- 31 | 32 | ####预备工作: 33 | 34 | 安装 Composer:下载[安装包(win)](https://getcomposer.org/Composer-Setup.exe),或访问 [getcomposer.org 官网](http://getcomposer.org)。 35 | 36 | ####安装 Yii-qa: 37 | 38 | 开启命令行切换到程序根目录并依次运行以下两条命令: 39 | 40 | ```console 41 | composer global require "fxp/composer-asset-plugin:1.0.0-beta3" 42 | composer create-project --prefer-dist --stability=dev yii2-chinesization/yii-qa yii-qa 43 | ``` 44 | 45 | >注意:如果遭遇到防火墙脑抽,或中国移不动,网不通的时候,可以使用宏大师搭建的墙内 Composer 镜像服务器,地址与使用请点击[Composer Proxy](http://composer-proxy.com/)。或歪果仁制作的商务加速服务[Toran Proxy中文版](http://pkg.phpcomposer.com/) 46 | 47 | ####额外: 48 | 49 | 如果你之前没存过的话,此时有可能需要你输入你的 GitHub Username & password。 50 | 51 | 示例如下: 52 | 53 | ```console 54 | The credentials will be swapped for an OAuth token stored in C:/Users/qiansen138 55 | 6/AppData/Roaming/Composer/auth.json, your password will not be stored 56 | To revoke access to this token you can visit https://github.com/settings/applica 57 | tions 58 | Username: ********* 59 | Password: 60 | ``` 61 | 62 | 依据网络状况不同,需耗时几分钟到十二亿五千万年……然后就安装完成了包括 Yii2 在内的所有依赖库。 63 | 64 | 程序本体的安装及初始化 65 | ------------ 66 | 67 | 安装步骤如下: 68 | - 在您的数据库系统中新建一个数据库(若已有,可跳过) 69 | - 重命名 `config/db-default.php` 文件 为 `db.php`,依据您的数据库配置相应地修改该文件。(请确认数据库已存在) 70 | - 打开命令行工具 -> 切换到程序根目录 71 | - 在程序根目录中执行 ``php yii migrate`` 执行数据迁移命令导入数据结构 72 | 73 | 执行截图如下: 74 | 75 | ![photo](https://cloud.githubusercontent.com/assets/1625891/4351508/2f1f60bc-420d-11e4-81a9-d2f0afdaed26.png) 76 | 77 | 78 | 注意 79 | ------------ 80 | 产品目前还是处于刚开发阶段.很多都未完善,如果您对Yii2框架有兴趣,可以参考程序代码来学习Yii2。 81 | 82 | 反馈或贡献代码 83 | ------------ 84 | 您可以在[这里](https://github.com/yii2-chinesization/yii-QA/issues)给我们提出在使用中碰到的问题或Bug。 85 | 86 | 本项目住持——CallMeZ大神承诺在第一时间回复您并修复。 87 | 88 | 你也可以发送邮件**callme-z@qq.com**给CallMeZ住持留言并且说明您的问题。 89 | 90 | 如果你有更好代码实现,请 fork 此项目并发起您的 Pull-Request,我们会及时处理。感谢! 91 | 92 | 如果你有其他的好想法,也欢迎您加入这两个QQ群: Yii2中国交流群[**343188481**] Yii2-QA[**325243235**]参与讨论交流。 93 | 94 | >额外啰嗦:如果您有关于 Yii2 的推广或视频教程制作方面的点子,欢迎加群或来信qiansen1386@gmail.com。祝搬砖快乐^_^ 95 | -------------------------------------------------------------------------------- /assets/AdminLteAsset.php: -------------------------------------------------------------------------------- 1 | 9 | * @since 2.0 10 | */ 11 | class AppAsset extends AssetBundle 12 | { 13 | public $basePath = '@webroot'; 14 | public $baseUrl = '@web'; 15 | public $css = [ 16 | 'css/global.css', 17 | 'css/ui.css' 18 | ]; 19 | public $js = [ 20 | 'js/global.js', 21 | ]; 22 | public $depends = [ 23 | 'yii\web\YiiAsset', 24 | 'yii\bootstrap\BootstrapAsset', 25 | 'app\assets\ArtTemplateAsset' 26 | ]; 27 | } 28 | -------------------------------------------------------------------------------- /assets/ArtTemplateAsset.php: -------------------------------------------------------------------------------- 1 | 'time', 12 | ActiveRecord::EVENT_BEFORE_UPDATE => 'time', 13 | // ActiveRecord::EVENT_AFTER_FIND => 'time' 14 | ]; 15 | 16 | public $dateFormat = 'Y-m-d H:i:s'; 17 | 18 | /** 19 | * Evaluates the attribute value and assigns it to the current attributes. 20 | * @param Event $event 21 | */ 22 | public function evaluateAttributes($event) 23 | { 24 | if (!empty($this->attributes[$event->name])) { 25 | $attributes = (array)$this->attributes[$event->name]; 26 | foreach ($attributes as $attribute) { 27 | $this->owner->$attribute = $this->getValue($event, $this->owner->$attribute); 28 | } 29 | } 30 | } 31 | 32 | protected function getValue($event, $value = null) 33 | { 34 | if ($this->value instanceof Expression) { 35 | return $this->value; 36 | } elseif ($this->value !== null) { 37 | return call_user_func($this->value, $event); 38 | } elseif ($event->name == ActiveRecord::EVENT_AFTER_FIND) { 39 | return is_numeric($value) ? date($this->dateFormat, $value) : $value; 40 | } else { 41 | return is_numeric($value) ? $value : strtotime($value); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /codeception.yml: -------------------------------------------------------------------------------- 1 | paths: 2 | tests: tests 3 | log: tests/_log 4 | data: tests/_data 5 | helpers: tests/_helpers 6 | settings: 7 | bootstrap: _bootstrap.php 8 | suite_class: \PHPUnit_Framework_TestSuite 9 | memory_limit: 1024M 10 | log: true 11 | colors: true 12 | modules: 13 | config: 14 | Db: 15 | dsn: '' 16 | user: '' 17 | password: '' 18 | dump: tests/_data/dump.sql 19 | -------------------------------------------------------------------------------- /commands/HelloController.php: -------------------------------------------------------------------------------- 1 | 18 | * @since 2.0 19 | */ 20 | class HelloController extends Controller 21 | { 22 | /** 23 | * This command echoes what you have entered as the message. 24 | * @param string $message the message to be echoed. 25 | */ 26 | public function actionIndex($message = 'hello world') 27 | { 28 | echo $message . "\n"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /components/AssetBundle.php: -------------------------------------------------------------------------------- 1 | YII_DEBUG // debug模式时强制拷贝 8 | ]; 9 | } -------------------------------------------------------------------------------- /components/ComposerInstaller.php: -------------------------------------------------------------------------------- 1 | getComposer(); 21 | $extra = $composer->getPackage()->getExtra(); 22 | if (isset($extra['asset-installer-paths']['bower-asset-library'])){ 23 | $bowerAssetDir = $extra['asset-installer-paths']['bower-asset-library']; 24 | $cssFile = rtrim($bowerAssetDir, '/') . '/adminlte/css/AdminLTE.css'; 25 | if (file_exists($cssFile)) { 26 | $content = file_get_contents($cssFile); 27 | $regexp = '/(@import) (url)\(([^>]*?)\);/'; 28 | if (preg_match($regexp, $content)) { 29 | $content = preg_replace($regexp, '', $content); 30 | file_put_contents($cssFile, $content); 31 | echo "'AdminLTE.css' google api replace success.\n"; 32 | } 33 | } else { 34 | echo "'{$cssFile}' file is not exists.\n"; 35 | } 36 | } else { 37 | echo "'npm-asset-library' is not set.\n"; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /components/Controller.php: -------------------------------------------------------------------------------- 1 | getSession()->setFlash($type, $message); 20 | if ($url !== null) { 21 | Yii::$app->end(0, $this->redirect($url)); 22 | } 23 | } 24 | 25 | /** 26 | * @param $message 信息显示内容 27 | * @param string $type 信息显示类型, ['info', 'success', 'error', 'warning'] 28 | * @param null $redirect 跳转地址 29 | * @param null $resultType 信息显示格式 30 | * @return array|string 31 | */ 32 | public function message($message, $type = 'info', $redirect = null, $resultType = null) 33 | { 34 | $resultType === null && $resultType = Yii::$app->getRequest()->getIsAjax() ? 'json' : 'html'; 35 | is_array($redirect) && $redirect = Url::to($redirect); 36 | $data = [ 37 | 'type' => $type, 38 | 'message' => $message, 39 | 'redirect' => $redirect 40 | ]; 41 | 42 | if ($resultType === 'json') { 43 | Yii::$app->getResponse()->format = Response::FORMAT_JSON; 44 | return $data; 45 | } elseif ($resultType === 'html') { 46 | return $this->render('/common/message', $data); 47 | } 48 | } 49 | 50 | public $ajaxLayout = '/ajaxMain'; 51 | public function findLayoutFile($view) 52 | { 53 | if (($this->layout === null) && ($this->ajaxLayout !== false) && Yii::$app->getRequest()->getIsAjax()) { 54 | $this->layout = $this->ajaxLayout; 55 | } 56 | return parent::findLayoutFile($view); 57 | } 58 | } -------------------------------------------------------------------------------- /components/DbAuthManager.php: -------------------------------------------------------------------------------- 1 | from($this->itemChildTable) 13 | ->join('INNER JOIN', $this->itemTable, implode(' AND ', [ 14 | $this->itemTable . '.name=' . $this->itemChildTable . '.child', 15 | $this->itemTable . '.type = ' . $type 16 | ])); 17 | $parents = []; 18 | foreach ($query->all($this->db) as $row) { 19 | $parents[$row['parent']][] = $row['child']; 20 | } 21 | return $parents; 22 | } 23 | 24 | /** 25 | * @params bool $recursive 是否递归显示子角色权限 26 | * @inheritdoc 27 | */ 28 | public function getPermissionsByRole($roleName, $recursive = true) 29 | { 30 | $childrenList = $recursive ? $this->getChildrenList() : $this->getChildrenListOfType(Item::TYPE_PERMISSION); 31 | $result = []; 32 | $this->getChildrenRecursive($roleName, $childrenList, $result); 33 | if (empty($result)) { 34 | return []; 35 | } 36 | $query = (new Query)->from($this->itemTable)->where([ 37 | 'type' => Item::TYPE_PERMISSION, 38 | 'name' => array_keys($result), 39 | ]); 40 | $permissions = []; 41 | foreach ($query->all($this->db) as $row) { 42 | $permissions[$row['name']] = $this->populateItem($row); 43 | } 44 | return $permissions; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /components/db/ActiveRecord.php: -------------------------------------------------------------------------------- 1 | db->getQueryBuilder()->batchInsert($table, $columns, $rows); 14 | return $this->setSql('REPLACE' . substr($sql, strpos($sql, ' '))); 15 | } 16 | 17 | } 18 | 19 | ?> -------------------------------------------------------------------------------- /components/db/Connection.php: -------------------------------------------------------------------------------- 1 | open(); 12 | $command = new Command([ // 使用了继承了之后的Command类.. 13 | 'db' => $this, 14 | 'sql' => $sql, 15 | ]); 16 | 17 | return $command->bindValues($params); 18 | } 19 | } -------------------------------------------------------------------------------- /components/db/Migration.php: -------------------------------------------------------------------------------- 1 | db->driverName === 'mysql') { //Mysql 表选项 24 | $engine = $this->useTransaction ? 'InnoDB' : 'MyISAM'; 25 | $this->tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=' . $engine; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yii2-chinesization/yii-qa", 3 | "description": "基于 Yii2 实现的问答系统", 4 | "version ": "0.1.11-dev", 5 | "keywords": [ 6 | "yii2", 7 | "WebApp", 8 | "Social-question-and-answer", 9 | "StackOverFlow-like" 10 | ], 11 | "homepage": "https://github.com/yii2-chinesization/yii-qa/", 12 | "type": "project", 13 | "license": "BSD-3-Clause", 14 | "authors": [ 15 | { 16 | "name": "CallMeZ", 17 | "email": "callme-z@qq.com", 18 | "role": "项目主管负责人方丈住持大都督" 19 | }, 20 | { 21 | "name": "qiansen1386(东方孤思子)", 22 | "email": "qiansen1386@gmail.com", 23 | "role": "旗手酱油打了个杂" 24 | } 25 | ], 26 | "support": { 27 | "issues": "https://github.com/yii2-chinesization/yii-qa/issues?state=open", 28 | "forum": "http://www.yiichina.com/topic/", 29 | "wiki": "https://github.com/yii2-chinesization/yii-qa/wiki/", 30 | "source": "https://github.com/yii2-chinesization/yii-qa/" 31 | }, 32 | "minimum-stability": "dev", 33 | "require": { 34 | "php": ">=5.4.0", 35 | "yiisoft/yii2": "*", 36 | "yiisoft/yii2-jui": "*", 37 | "yiisoft/yii2-bootstrap": "*", 38 | "yiisoft/yii2-authclient": "*", 39 | "yiisoft/yii2-swiftmailer": "*", 40 | "callmez/yii2-storage": "*", 41 | "bower-asset/adminlte": "*", 42 | "bower-asset/pagedown": "*", 43 | "bower-asset/selectize": "*", 44 | "npm-asset/art-template": "*" 45 | }, 46 | "require-dev": { 47 | "yiisoft/yii2-codeception": "*", 48 | "yiisoft/yii2-debug": "*", 49 | "yiisoft/yii2-gii": "*", 50 | "yiisoft/yii2-faker": "*" 51 | }, 52 | "config": { 53 | "process-timeout": 1800 54 | }, 55 | "autoload": { 56 | "psr-4": { "app\\": "" } 57 | }, 58 | "scripts": { 59 | "post-create-project-cmd": [ 60 | "yii\\composer\\Installer::postCreateProject", 61 | "app\\components\\ComposerInstaller::initProject" 62 | ], 63 | "post-install-cmd": [ 64 | "app\\components\\ComposerInstaller::initProject" 65 | ], 66 | "post-update-cmd": [ 67 | "app\\components\\ComposerInstaller::initProject" 68 | ] 69 | }, 70 | "extra": { 71 | "yii\\composer\\Installer::postCreateProject": { 72 | "setPermission": [ 73 | { 74 | "runtime": "0777", 75 | "web/assets": "0777", 76 | "yii": "0755" 77 | } 78 | ], 79 | "generateCookieValidationKey": [ 80 | "config/web.php" 81 | ] 82 | }, 83 | "asset-installer-paths": { 84 | "npm-asset-library": "vendor/npm", 85 | "bower-asset-library": "vendor/bower" 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /config/assets.php: -------------------------------------------------------------------------------- 1 | 'java -jar tools/compiler.jar --js {from} --js_output_file {to}', 12 | // Adjust command/callback for CSS files compressing: 13 | 'cssCompressor' => 'java -jar tools/yuicompressor.jar --type css {from} -o {to}', 14 | // The list of asset bundles to compress: 15 | 'bundles' => [ 16 | 'yii\web\YiiAsset', 17 | 'yii\web\JqueryAsset', 18 | ], 19 | // Asset bundle for compression output: 20 | 'targets' => [ 21 | 'app\assets\AllAsset' => [ 22 | 'basePath' => $basePath, 23 | 'baseUrl' => $baseUrl, 24 | 'js' => 'all-{ts}.js', 25 | 'css' => 'all-{ts}.css', 26 | ], 27 | ], 28 | // Asset manager configuration: 29 | 'assetManager' => [ 30 | 'basePath' => $basePath, 31 | 'baseUrl' => $baseUrl, 32 | ], 33 | ]; -------------------------------------------------------------------------------- /config/console.php: -------------------------------------------------------------------------------- 1 | 'basic-console', 10 | 'basePath' => dirname(__DIR__), 11 | 'bootstrap' => ['log'], 12 | 'controllerNamespace' => 'app\commands', 13 | 'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'), 14 | 'modules' => require(__DIR__ . '/modules.php'), 15 | 'components' => [ 16 | 'cache' => [ 17 | 'class' => 'yii\caching\FileCache', 18 | ], 19 | 'log' => [ 20 | 'targets' => [ 21 | [ 22 | 'class' => 'yii\log\FileTarget', 23 | 'levels' => ['error', 'warning'], 24 | ], 25 | ], 26 | ], 27 | 'config' => [ 28 | 'class' => 'app\components\Config', 29 | 'loadModel' => 'app\models\Config' 30 | ], 31 | 'authManager' => [ 32 | 'class' => 'yii\rbac\DbManager', 33 | ], 34 | 'db' => $db, 35 | ], 36 | 'params' => $params, 37 | ]; 38 | 39 | return $config; -------------------------------------------------------------------------------- /config/db-default.php: -------------------------------------------------------------------------------- 1 | 'app\components\db\Connection', 8 | 'dsn' => 'mysql:host=localhost;dbname=yii_qa', 9 | 'username' => 'root', 10 | 'password' => '', 11 | 'tablePrefix' => 'pre_', 12 | 'charset' => 'utf8', 13 | ]; -------------------------------------------------------------------------------- /config/modules.php: -------------------------------------------------------------------------------- 1 | [ // 用户模块 4 | 'class' => 'app\modules\user\Module', 5 | ], 6 | 'admin' => [ 7 | 'class' => 'app\modules\admin\Module', 8 | ], 9 | 'forum' => [ 10 | 'class' => 'app\modules\forum\Module', 11 | ], 12 | 'wechat' => [ 13 | 'class' => 'app\modules\wechat\Module', 14 | ], 15 | ] 16 | ?> -------------------------------------------------------------------------------- /config/params.php: -------------------------------------------------------------------------------- 1 | 'admin@example.com', 6 | ]; 7 | -------------------------------------------------------------------------------- /config/rewrite.php: -------------------------------------------------------------------------------- 1 | '/user/default/login', 4 | 'signup' => '/user/default/register', 5 | 'signout' => '/user/default/logout', 6 | 'auth' => '/user/default/auth', 7 | 'reset-password' => '/user/default/resetPassword', 8 | 'user/' => '/user/home/index', 9 | 10 | 11 | //标签rest 12 | // 'PUT,PATCH tags/' => 'tag/update', 13 | 'GET,HEAD tags//' => 'tag/index', //tag 关键字检索 14 | 'DELETE tags/' => 'tag/delete', 15 | 'GET,HEAD tags/' => 'tag/view', 16 | 'POST tags' => 'tags/create', 17 | 'GET,HEAD tags' => 'tag/index', 18 | // 'tags/' => 'tag/options', 19 | // 'tags' => 'tag/options', 20 | ]; 21 | ?> -------------------------------------------------------------------------------- /config/web.php: -------------------------------------------------------------------------------- 1 | 'basic', 8 | 'name' => 'Yii', 9 | 'basePath' => dirname(__DIR__), 10 | 'language' => 'zh-CN', 11 | 'bootstrap' => ['log'], 12 | 'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'), 13 | 'modules' => require(__DIR__ . '/modules.php'), 14 | 'defaultRoute' => 'forum', 15 | 'components' => [ 16 | 'request' => [ 17 | 'cookieValidationKey' => 'callmez', 18 | ], 19 | 'cache' => [ 20 | 'class' => 'yii\caching\FileCache', 21 | ], 22 | 'user' => [ 23 | 'class' => 'app\modules\user\components\User', 24 | 'identityClass' => 'app\modules\user\models\User', 25 | 'enableAutoLogin' => true, 26 | ], 27 | 'errorHandler' => [ 28 | 'errorAction' => 'site/error', 29 | ], 30 | 'mail' => [ 31 | 'class' => 'yii\swiftmailer\Mailer', 32 | 'useFileTransport' => true, 33 | ], 34 | 'log' => [ 35 | 'traceLevel' => YII_DEBUG ? 3 : 0, 36 | 'targets' => [ 37 | [ 38 | 'class' => 'yii\log\FileTarget', 39 | 'levels' => ['trace', 'error', 'warning'], 40 | ], 41 | ], 42 | ], 43 | 'config' => [ 44 | 'class' => 'app\components\Config', 45 | 'loadModel' => 'app\models\Config' 46 | ], 47 | 'urlManager' => array( 48 | 'showScriptName' => false, 49 | 'enablePrettyUrl' => true, 50 | 'rules' => require(__DIR__ . '/rewrite.php') 51 | ), 52 | 'authManager' => [ 53 | 'class' => 'app\components\DbAuthManager', 54 | ], 55 | // 'authClientCollection' => [ 56 | // 'class' => 'yii\authclient\Collection', 57 | // 'clients' => [ 58 | // 'weibo' => [ 59 | // 'class' => 'app\modules\user\components\authclient\clients\WeiBo', 60 | // 'clientId' => '991747086', 61 | // 'clientSecret' => '61ee61c2e2b2d0ad9bb4024e87478d59', 62 | // ], 63 | // 'qq' => [ 64 | // 'class' => 'app\modules\user\components\authclient\clients\QQ', 65 | // 'clientId' => '101119657', 66 | // 'clientSecret' => 'b7dd03879ee20c0c209f678db2d0d1fa', 67 | // ], 68 | // ], 69 | // ], 70 | 'storageCollection' => [ 71 | 'class' => 'yii\storage\Collection', 72 | // 'defaultStorage' => 'qiniu', 73 | 'bin' => [ 74 | 'qiniu' => [ 75 | 'class' => 'yii\storage\bin\QiniuStorage', 76 | 'bucket' => 'records', 77 | 'accessKey' => 'pn6VrDeikA7cbU-cSjH6_1XGKaERXcHKBMgb5k9L', 78 | 'secretKey' => 'ovSuXqHBHHESKo2goAD1HXgS68ag4Di_NZDZep_h' 79 | ] 80 | ] 81 | ], 82 | 'db' => $db, 83 | 'wechat' => [ 84 | 'class' => 'yii\wechat\sdk\Wechat', 85 | 'appId' => 'wxcb80aa7944a936e0', 86 | 'appSecret' => '9512b97b84615a81d554f211761e4a4b', 87 | 'token' => '123' 88 | ] 89 | ], 90 | 'params' => $params, 91 | ]; 92 | 93 | if (YII_ENV_DEV) { 94 | // configuration adjustments for 'dev' environment 95 | $config['bootstrap'][] = 'debug'; 96 | $config['modules']['gii'] = 'yii\gii\Module'; 97 | $config['modules']['debug'] = [ 98 | 'class' => 'yii\debug\Module', 99 | 'allowedIPs' => ['*'] 100 | ]; 101 | } elseif (YII_ENV_PROD) { 102 | $config['compoents']['assetManager']['bundles'] = require(__DIR__ . '/assets_compressed.php'); 103 | } 104 | return $config; 105 | -------------------------------------------------------------------------------- /controllers/SiteController.php: -------------------------------------------------------------------------------- 1 | [ 21 | 'class' => AccessControl::className(), 22 | 'only' => ['logout'], 23 | 'rules' => [ 24 | [ 25 | 'actions' => ['logout'], 26 | 'allow' => true, 27 | 'roles' => ['@'], 28 | ], 29 | ], 30 | ], 31 | 'verbs' => [ 32 | 'class' => VerbFilter::className(), 33 | 'actions' => [ 34 | 'logout' => ['post'], 35 | ], 36 | ], 37 | ]; 38 | } 39 | 40 | public function actions() 41 | { 42 | return [ 43 | 'error' => [ 44 | 'class' => 'yii\web\ErrorAction', 45 | ], 46 | 'captcha' => [ 47 | 'class' => 'yii\captcha\CaptchaAction', 48 | 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, 49 | ], 50 | ]; 51 | } 52 | 53 | public function actionIndex() 54 | { 55 | return $this->render('index'); 56 | } 57 | 58 | public function actionContact() 59 | { 60 | $model = new ContactForm(); 61 | if ($model->load(Yii::$app->request->post()) && $model->contact(Yii::$app->params['adminEmail'])) { 62 | Yii::$app->session->setFlash('contactFormSubmitted'); 63 | 64 | return $this->refresh(); 65 | } else { 66 | return $this->render('contact', [ 67 | 'model' => $model, 68 | ]); 69 | } 70 | } 71 | 72 | public function actionAbout() 73 | { 74 | return $this->render('about'); 75 | } 76 | 77 | public function actionTest() 78 | { 79 | $db = Yii::$app->db; 80 | // echo \yii\helpers\Markdown::process('**strong text***emphasized text*');exit; 81 | // echo Yii::getAlias('@yii/storage'); 82 | return $this->render('test'); 83 | } 84 | } -------------------------------------------------------------------------------- /helpers/Console.php: -------------------------------------------------------------------------------- 1 | params['optimizeUrl'])) { 22 | return Yii::$app->params['optimizeUrl'] . $path; 23 | } else { 24 | return Yii::$app->getHomeUrl() . $path; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /mail/layouts/html.php: -------------------------------------------------------------------------------- 1 | 9 | beginPage() ?> 10 | 11 | 12 | 13 | 14 | <?= Html::encode($this->title) ?> 15 | head() ?> 16 | 17 | 18 | beginBody() ?> 19 | 20 | endBody() ?> 21 | 22 | 23 | endPage() ?> -------------------------------------------------------------------------------- /migrations/data/Data ERD.mwb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yii2-chinesization/yii-qa/131211ae7643b3787cc1870488c03bc9def8e6de/migrations/data/Data ERD.mwb -------------------------------------------------------------------------------- /migrations/m140910_000000_initTable.php: -------------------------------------------------------------------------------- 1 | createTable($tableName, [ 18 | 'name' => Schema::TYPE_STRING . "(64) NOT NULL COMMENT '名称'", 19 | 'value' => Schema::TYPE_TEXT . " NOT NULL COMMENT '保存的值'", 20 | 'PRIMARY KEY (name)', 21 | ], $this->tableOptions); 22 | 23 | //附件表 24 | $tableName = Storage::tableName(); 25 | $this->createTable($tableName, [ 26 | 'id' => Schema::TYPE_PK, 27 | 'uid' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '用户id'", 28 | 'name' => Schema::TYPE_STRING . " NOT NULL DEFAULT '' COMMENT '原始文件名'", 29 | 'path' => Schema::TYPE_STRING . " NOT NULL DEFAULT '' COMMENT '保存路径'", 30 | 'size' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '文件大小'", 31 | 'mime_type' => Schema::TYPE_STRING . "(50) NOT NULL DEFAULT '' COMMENT '文件类型'", 32 | 'bin' => Schema::TYPE_STRING . "(30) NOT NULL DEFAULT '' COMMENT '存储容器'", 33 | 'type' => Schema::TYPE_STRING . "(50) NOT NULL DEFAULT '' COMMENT '所属类型'", 34 | 'status' => Schema::TYPE_BOOLEAN . " NOT NULL DEFAULT '0' COMMENT '附件存储状态'", 35 | 'created_at' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间'", 36 | 'updated_at' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '修改时间'", 37 | ], $this->tableOptions); 38 | $this->createIndex('uid', $tableName, 'uid'); 39 | $this->createIndex('type', $tableName, ['type', 'id']); 40 | 41 | //标签表 42 | $tableName = Tag::tableName(); 43 | $this->createTable($tableName, [ 44 | 'id' => Schema::TYPE_PK, 45 | 'name' => Schema::TYPE_STRING . "(64) NOT NULL COMMENT '标签名'", 46 | 'icon' => Schema::TYPE_STRING . " NOT NULL DEFAULT '' COMMENT '标签图标'", 47 | 'description' => Schema::TYPE_TEXT . " NOT NULL COMMENT '版块介绍'", 48 | 'status' => Schema::TYPE_BOOLEAN . " NOT NULL DEFAULT '0' COMMENT '标签状态'", 49 | 'created_at' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间'", 50 | 'updated_at' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '修改时间'", 51 | ]); 52 | $this->createIndex('name', $tableName, 'name', true); 53 | $this->createIndex('status', $tableName, ['status', 'id']); 54 | 55 | //标签数据表 56 | $tableName = TagItem::tableName(); 57 | $this->createTable($tableName, [ 58 | 'id' => Schema::TYPE_PK, 59 | 'tid' => Schema::TYPE_STRING . "(64) NOT NULL COMMENT '标签id'", 60 | 'target_id' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '目标id'", 61 | 'target_type' => Schema::TYPE_STRING . "(100) NOT NULL DEFAULT '' COMMENT '目标类型'", 62 | 'created_at' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间'" 63 | ]); 64 | $this->createIndex('item', $tableName, ['tid', 'target_id', 'target_type'], true); 65 | $this->createIndex('target_type', $tableName, ['target_type', 'target_id']); 66 | } 67 | 68 | public function down() 69 | { 70 | $this->dropTable(Config::tableName()); 71 | $this->dropTable(Storage::tableName()); 72 | } 73 | 74 | public function dbInit() 75 | {exit; 76 | if (Yii::$app->db->dsn !== null) { 77 | Yii::$app->db->open(); 78 | return; 79 | } 80 | Console::output('需要初始并生成数据库设置 ....'); 81 | $dbHost = Console::prompt('请输入数据库地址', ['default' => 'localhost']); 82 | $dbName = Console::prompt('请输入数据库名称(并确定数据库已建立)', ['default' => Yii::$app->name]); 83 | $dbUsername = Console::prompt('请输入数据库账户名', ['default' => 'root']); 84 | $dbPassword = Console::prompt('请输入数据库密码(默认为空)', ['default' => '']); 85 | $dbPrefix = Console::prompt('请输入数据库表前缀', ['default' => 'pre_']); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /migrations/m140910_000001_initRbacTable.php: -------------------------------------------------------------------------------- 1 | isDbAuth()) { 15 | parent::up(); 16 | $this->rbacInit(); 17 | } 18 | } 19 | 20 | public function down() 21 | { 22 | $this->isDbAuth() && parent::down(); 23 | } 24 | 25 | /** 26 | * 判断是否db类型的authManager. 27 | * @return 28 | */ 29 | public function isDbAuth() 30 | { 31 | static $isDbAuth; 32 | if ($isDbAuth === null) { 33 | $auth = Yii::$app->authManager; 34 | $isDbAuth = $auth && property_exists($auth, 'db'); 35 | } 36 | return $isDbAuth; 37 | } 38 | 39 | /** 40 | * 初始化rbac 默认设置 41 | */ 42 | public function rbacInit() 43 | { 44 | Console::output('初始化RBAC数据 ....'); 45 | $auth = Yii::$app->authManager; 46 | 47 | /* ================= 权限 ================= */ 48 | $visitAdmin = $auth->createPermission('visitAdmin'); 49 | $visitAdmin->description = '访问后台管理界面'; 50 | $auth->add($visitAdmin); 51 | 52 | /* ================= 身份 ================= */ 53 | $guest = $auth->createRole('guest'); // 匿名用户 54 | $guest->description = '匿名用户'; 55 | $auth->add($guest); 56 | 57 | $user = $auth->createRole('user'); //普通用户 58 | $user->description = '普通用户'; 59 | $auth->add($user, $guest); //普通用户 > 匿名用户 60 | 61 | $admin = $auth->createRole('admin'); // 管理员 62 | $admin->description = '管理员'; 63 | $auth->add($admin); 64 | $auth->addChild($admin, $user); // 管理员 > 普通用户 65 | $auth->addChild($admin, $visitAdmin); // 管理员可以访问后台 66 | 67 | $founder = $auth->createRole('founder'); // 创始人 68 | $founder->description = '创始人'; 69 | $auth->add($founder); 70 | $auth->addChild($founder, $admin); // 创始人 > 管理员 71 | 72 | Console::output('初始化RBAC数据完成 ....'); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /migrations/m140910_100100_initAdminTable.php: -------------------------------------------------------------------------------- 1 | initMenu(); 13 | } 14 | 15 | public function down() 16 | { 17 | } 18 | 19 | public function initMenu() 20 | { 21 | Console::output('初始化后台菜单 ....'); 22 | /* ============= 添加后台菜单 ============= */ 23 | //用户 24 | AdminHelper::addMenu('user', ['/user/admin/user/index'], '用户管理', [ 25 | 'icon' => 'fa-user', 26 | 'priority' => 10 27 | ]); 28 | 29 | //rbac 30 | AdminHelper::addMenu('rbac', ['/user/admin/rbac/index'], '角色权限', [ 31 | 'subShow' => false, 32 | 'icon' => 'fa-group', 33 | 'priority' => 20 34 | ]); 35 | AdminHelper::addSubMenu('rbac', 'roles', ['/user/admin/rbac/roles'], '角色列表'); 36 | AdminHelper::addSubMenu('rbac', 'permissions', ['/user/admin/rbac/permissions'], '权限列表'); 37 | 38 | //系统设置 39 | AdminHelper::addMenu('system', ['/admin/system/index'], '系统设置', [ 40 | 'icon' => 'fa-gears', 41 | 'priority' => 20 42 | ]); 43 | 44 | Console::output('初始化后台菜单完成 ....'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /migrations/m140910_100300_initForumTable.php: -------------------------------------------------------------------------------- 1 | createTable($tableName, [ 17 | 'id' => Schema::TYPE_PK, 18 | 'parent' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '父版块'", 19 | 'name' => Schema::TYPE_STRING . "(100) NOT NULL COMMENT '版块名称'", 20 | 'description' => Schema::TYPE_TEXT . " NOT NULL COMMENT '版块介绍'", 21 | 'cover' => Schema::TYPE_STRING . " NOT NULL DEFAULT '' COMMENT '版块封面'", 22 | 'icon' => Schema::TYPE_STRING . " NOT NULL DEFAULT '' COMMENT '版块图标'", 23 | 'topic_count' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '话题数'", 24 | 'created_at' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间'", 25 | 'updated_at' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '修改时间'", 26 | ], $this->tableOptions); 27 | $this->createIndex('parent', $tableName, 'parent', true); 28 | $this->forumInit(); 29 | 30 | //话题 31 | $tableName = Topic::tableName(); 32 | $this->createTable($tableName, [ 33 | 'id' => Schema::TYPE_PK, 34 | 'fid' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '版块ID'", 35 | 'tid' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '所属话题ID.默认为0, 表示为话题,否则为评论'", 36 | 'author_id' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '作者ID'", 37 | 'subject' => Schema::TYPE_STRING . " NOT NULL COMMENT '话题的主题'", 38 | 'content' => Schema::TYPE_TEXT . " NOT NULL COMMENT '话题内容'", 39 | 'view_count' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '查看数'", 40 | 'comment_count' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '评论数'", 41 | 'favorite_count' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '收藏数'", 42 | 'like_count' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '喜欢数'", 43 | 'hate_count' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '讨厌数'", 44 | 'status' => Schema::TYPE_BOOLEAN . " NOT NULL DEFAULT '0' COMMENT 'status'", 45 | 'created_at' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间'", 46 | 'updated_at' => Schema::TYPE_INTEGER . " UNSIGNED NOT NULL DEFAULT '0' COMMENT '修改时间'", 47 | ]); 48 | $this->createIndex('fid', $tableName, 'fid'); 49 | $this->topicInit(); 50 | 51 | $this->commentInit(); 52 | } 53 | 54 | public function down() 55 | { 56 | $this->dropTable(Forum::tableName()); 57 | $this->dropTable(Topic::tableName()); 58 | } 59 | 60 | public $forumId; 61 | public function forumInit() 62 | { 63 | Console::output('创建默认版块 ....'); 64 | $forum = new Forum(); 65 | $forum->setAttributes([ 66 | 'name' => '默认版块', 67 | 'description' => '默认版块描述' 68 | ]); 69 | if ($forum->save()) { 70 | $message = '成功'; 71 | $this->forumId = $forum->primaryKey; 72 | } else { 73 | $message = '失败'; 74 | } 75 | Console::output('创建默认版块' . $message); 76 | } 77 | 78 | public $topic; 79 | public function topicInit() 80 | { 81 | if ($this->forumId === null) { 82 | Console::output('无法创建默认话题,因为没有默认归属版块 ....'); 83 | return; 84 | } 85 | Console::output('创建默认话题 ....'); 86 | $topic = new Topic(); 87 | $topic->setAttributes([ 88 | 'fid' => $this->forumId, 89 | 'author_id' => 1, 90 | 'subject' => '默认话题', 91 | 'content' => '默认话题内容' 92 | ]); 93 | if ($topic->save()) { 94 | $topic->setActive(); //激活 95 | $message = '成功'; 96 | $this->topic = $topic; 97 | } else { 98 | $message = '失败'; 99 | } 100 | Console::output('创建默认话题' . $message); 101 | } 102 | 103 | public function commentInit() 104 | { 105 | if (!$this->topic) { 106 | return; 107 | } 108 | Console::output('创建默认评论 ....'); 109 | $comment = new Comment(); 110 | $comment->setAttributes([ 111 | 'author_id' => 1, 112 | 'content' => '默认评论内容' 113 | ]); 114 | 115 | $message = $this->topic->addComment($comment, true) ? '成功' : '失败'; 116 | Console::output('创建默认评论' . $message); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /models/Config.php: -------------------------------------------------------------------------------- 1 | setAttribute('value', serialize($this->getAttribute('value'))); // 序列化保存 value 数据 20 | parent::beforeSave($insert); 21 | } 22 | 23 | public function afterFind() 24 | { 25 | $this->setAttribute('value', unserialize($this->getAttribute('value'))); // value 数据反序列化 26 | parent::afterFind(); 27 | } 28 | 29 | /** 30 | * 按数组格式取出值, 作为Config调用 [ 31 | * 'name' => 'value' 32 | * ] 33 | * @return array 34 | */ 35 | public static function getData() 36 | { 37 | return ArrayHelper::map( 38 | static::find()->all(), 39 | 'name', 40 | 'value' 41 | ); 42 | } 43 | 44 | 45 | /** 46 | * Config 47 | * @param array $data 48 | */ 49 | public static function saveData(array $data) 50 | { 51 | $newData = []; 52 | foreach ($data as $name => $value) { 53 | $newData[] = [$name, serialize($value)]; // 序列化保存 value 数据 54 | } 55 | return static::getDb()->createCommand()->batchReplace(static::tableName(), [ 56 | 'name', 57 | 'value' 58 | ], $newData, true)->execute(); 59 | } 60 | } 61 | 62 | ?> -------------------------------------------------------------------------------- /models/ContactForm.php: -------------------------------------------------------------------------------- 1 | 'Verification Code', 41 | ]; 42 | } 43 | 44 | /** 45 | * Sends an email to the specified email address using the information collected by this model. 46 | * @param string $email the target email address 47 | * @return boolean whether the model passes validation 48 | */ 49 | public function contact($email) 50 | { 51 | if ($this->validate()) { 52 | Yii::$app->mail->compose() 53 | ->setTo($email) 54 | ->setFrom([$this->email => $this->name]) 55 | ->setSubject($this->subject) 56 | ->setTextBody($this->body) 57 | ->send(); 58 | return true; 59 | } else { 60 | return false; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /models/Storage.php: -------------------------------------------------------------------------------- 1 | [ 26 | 'class' => 'yii\behaviors\TimestampBehavior', 27 | ], 28 | ]; 29 | } 30 | 31 | public function getUrl() 32 | { 33 | return Url::to('@web/storage' . $this->path); 34 | } 35 | } -------------------------------------------------------------------------------- /models/Tag.php: -------------------------------------------------------------------------------- 1 | ''], 32 | ]; 33 | } 34 | 35 | public static function find() 36 | { 37 | return (new TagQuery(get_called_class())); 38 | } 39 | 40 | public function behaviors() 41 | { 42 | return [ 43 | 'timestamp' => [ 44 | 'class' => 'yii\behaviors\TimestampBehavior', 45 | ], 46 | ]; 47 | } 48 | 49 | /** 50 | * 审核通过 51 | * @return bool 52 | */ 53 | public function setActive() 54 | { 55 | $this->status != static::STATUS_ACTIVE && $this->updateAttributes(['status' => static::STATUS_ACTIVE]); 56 | return true; 57 | } 58 | 59 | public function getTagItems() 60 | { 61 | } 62 | } -------------------------------------------------------------------------------- /models/TagItem.php: -------------------------------------------------------------------------------- 1 | [ 24 | 'class' => 'yii\behaviors\TimestampBehavior', 25 | 'attributes' => [ 26 | ActiveRecord::EVENT_BEFORE_INSERT => 'created_at', 27 | ], 28 | ], 29 | ]; 30 | } 31 | 32 | public function getTag() 33 | { 34 | return $this->hasOne(Tag::className(), ['tid' => 'id']); 35 | } 36 | } -------------------------------------------------------------------------------- /models/TagQuery.php: -------------------------------------------------------------------------------- 1 | andWhere(['status' => Tag::STATUS_ACTIVE]); 11 | return $this; 12 | } 13 | } -------------------------------------------------------------------------------- /models/TagSearch.php: -------------------------------------------------------------------------------- 1 | $query, 48 | ]); 49 | 50 | if (!($this->load($params) && $this->validate())) { 51 | return $dataProvider; 52 | } 53 | 54 | $query->andFilterWhere([ 55 | 'id' => $this->id 56 | ]); 57 | 58 | $query->andFilterWhere(['like', 'name', $this->name]); 59 | 60 | return $dataProvider; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /modules/admin/Module.php: -------------------------------------------------------------------------------- 1 | :first-child').prepend( 4 | // $('.sidebar-menu > li.active, .treeview-menu li.active') 5 | // .last() 6 | // .find('a') 7 | // .text() 8 | // || $('.logo').text()); 9 | }); -------------------------------------------------------------------------------- /modules/admin/components/Controller.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'class' => AccessControl::className(), 15 | 'rules' => [ 16 | [ 17 | 'allow' => true, 18 | 'roles' => ['@'], 19 | ], 20 | ], 21 | ] 22 | ]; 23 | } 24 | 25 | public function message($message, $type = 'info', $url = null, $resultType = null) 26 | { 27 | if ($resultType !== null) { 28 | return $this->message($message, $type, $resultType); 29 | } 30 | 31 | } 32 | } -------------------------------------------------------------------------------- /modules/admin/controllers/DefaultController.php: -------------------------------------------------------------------------------- 1 | render('index'); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /modules/admin/controllers/UserController.php: -------------------------------------------------------------------------------- 1 | render('index', [ 14 | 'userDataProvider' => new ActiveDataProvider([ 15 | 'query' => User::find() 16 | ]) 17 | ]); 18 | } 19 | } -------------------------------------------------------------------------------- /modules/admin/helpers/AdminHelper.php: -------------------------------------------------------------------------------- 1 | get('config')->get('menu', []); 14 | } 15 | 16 | public static function setMenu($menu) 17 | { 18 | return Yii::$app->get('config')->set('menu', $menu); 19 | } 20 | 21 | /** 22 | * 添加菜单(如果菜单已存在则覆盖) 23 | * @param $name 菜单键值 24 | * @param $link 25 | * @param array $options 26 | */ 27 | public static function addMenu($menuKey, $link, $title = null, array $options = []) 28 | { 29 | $menus = static::getMenu(); 30 | 31 | $menu = [ 32 | 'link' => $link, 33 | 'title' => $title ? : $menuKey, 34 | 'icon' => isset($options['icon']) ? $options['icon'] : 'fa-gear', 35 | 'priority' => isset($options['priority']) ? (int)$options['priority'] : 10 // 优先级 36 | ]; 37 | 38 | if (isset($options['parent'])) { //子菜单 39 | if (!array_key_exists($options['parent'], $menus)) { 40 | throw new InvalidConfigException("The menu {$options['parent']} is not exists, Can't set submenu."); 41 | } 42 | $menus[$options['parent']]['submenu'][$menuKey] = $menu; 43 | ArrayHelper::multisort($menus[$options['parent']]['submenu'], 'priority'); // 子菜单排序 44 | } else { 45 | $menu['subShow'] = isset($options['subShow']) && $options['subShow'] !== false; // 是否在子菜单显示 46 | 47 | !isset($menus[$menuKey]) && $menus[$menuKey] = []; 48 | $menus[$menuKey] = array_merge($menus[$menuKey], $menu); 49 | ArrayHelper::multisort($menus[$menuKey], 'priority'); // 菜单排序 50 | } 51 | 52 | return static::setMenu($menus); 53 | } 54 | 55 | /** 56 | * 添加子菜单 57 | * @param $parent 58 | * @param $title 59 | * @param $link 60 | * @param array $options 61 | */ 62 | public static function addSubmenu($parent, $menuKey, $link, $title = null, array $options = []) 63 | { 64 | $options['parent'] = $parent; 65 | !isset($options['icon']) && $options['icon'] = 'fa-angle-double-right'; 66 | return static::addMenu($menuKey, $link, $title, $options); 67 | } 68 | } -------------------------------------------------------------------------------- /modules/admin/models/AuthChildItemForm.php: -------------------------------------------------------------------------------- 1 | false], 23 | ['add', 'safe'] 24 | ]; 25 | } 26 | 27 | /** 28 | * @param $attribute 29 | * @param $params 30 | */ 31 | public function checkItem($attribute, $params) 32 | { 33 | if (!($this->$attribute instanceof Item)) { 34 | $this->addError($attribute, "{$attribute}必须为\yii\rbac\Item实例"); 35 | } 36 | } 37 | 38 | public function saveChildItem() 39 | { 40 | if (!$this->validate()) { 41 | return false; 42 | } 43 | $authManager = Yii::$app->getAuthManager(); 44 | if ($this->add) { // 添加 45 | if ($authManager->hasChild($this->parent, $this->child)) { 46 | return true; // 已存在的直接返回成功 47 | $this->addError('child', "{$this->child->name}已经是{$this->parent->name}的子" . ($this->child->type == Item::TYPE_ROLE ? '角色' : '权限')); 48 | return false; 49 | } 50 | return $authManager->addChild($this->parent, $this->child); 51 | } else { // 删除 52 | return $authManager->removeChild($this->parent, $this->child); 53 | } 54 | 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /modules/admin/models/AuthItemForm.php: -------------------------------------------------------------------------------- 1 | '/[A-Za-z0-9_]+/i', 'message' => '关键字只能使用字母数字和-_符号'], 48 | ['ruleName', 'checkRuleName', 'skipOnEmpty' => false], 49 | ['data', 'safe'] 50 | ]; 51 | } 52 | 53 | /** 54 | * ruleName 有外键约束 空字符串不能通过, 设置为null 55 | * @param $attribute 56 | * @param $params 57 | */ 58 | public function checkRuleName($attribute, $params) 59 | { 60 | $this->$attribute = $this->$attribute !== '' ? $this->$attribute : null; 61 | } 62 | 63 | public function attributeLabels() 64 | { 65 | return [ 66 | 'type' => '类型', 67 | 'name' => '关键字', 68 | 'description' => $this->type == Item::TYPE_ROLE ? '角色名称' : '权限描述', 69 | 'ruleName' => '规则名', 70 | 'data' => '内容', 71 | 'createdAt' => '创建时间', 72 | 'updatedAt' => '修改时间' 73 | ]; 74 | } 75 | 76 | /** 77 | * 创建或修改item 78 | * @return bool 79 | */ 80 | public function saveItem(Item $item = null) 81 | { 82 | if (!$this->validate()) { 83 | return false; 84 | } 85 | $authManager = Yii::$app->getAuthManager(); 86 | 87 | if ($item === null) { // 创建Item 88 | if ($this->type == Item::TYPE_ROLE) { // 判断类型 89 | $methodGet = 'getRole'; 90 | $methodCreate = 'createRole'; 91 | } else { 92 | $methodGet = 'getPermission'; 93 | $methodCreate = 'createPermission'; 94 | } 95 | if ($item = $authManager->$methodGet($this->name)) { 96 | return $this->addError('name', '关键字已存在'); 97 | } 98 | $item = $authManager->$methodCreate($this->name); // 否则创建item 99 | Yii::configure($item, $this->getAttributes()); 100 | return $authManager->add($item); 101 | } else { // 修改item 102 | if ($this->type != $item->type) { //类型必须一样 103 | $this->addError('name', "{$item->name}类型不正确.不能修改"); 104 | } 105 | $name = $item->name; 106 | Yii::configure($item, $this->getAttributes()); 107 | return $authManager->update($name, $item); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /modules/admin/views/default/index.php: -------------------------------------------------------------------------------- 1 |
2 |

context->action->uniqueId ?>

3 | 4 |

5 | This is the view content for action "context->action->id ?>". 6 | The action belongs to the controller "context) ?>" 7 | in the "context->module->id ?>" module. 8 |

9 | 10 |

11 | You may customize this page by editing the following file:
12 | 13 |

14 |
15 | -------------------------------------------------------------------------------- /modules/admin/views/user/index.php: -------------------------------------------------------------------------------- 1 | title = '用户管理'; 6 | $this->params['breadcrumbs'] = [ 7 | $this->title 8 | ]; 9 | 10 | ?> 11 |
12 |
13 | $userDataProvider, 15 | 'button' => Html::a('添加用户', ['/admin/user/add'], ['class' => 'btn btn-primary pull-right']), 16 | 'columns' => [ 17 | [ 18 | 'attribute' => 'id', 19 | 'options' => [ 20 | 'width' => 30 21 | ] 22 | ], 23 | [ 24 | 'attribute' => 'username', 25 | ], 26 | [ 27 | 'attribute' => 'email', 28 | ], 29 | [ 30 | 'attribute' => 'last_login_ip', 31 | 'options' => [ 32 | 'width' => 100 33 | ] 34 | ], 35 | [ 36 | 'attribute' => 'status', 37 | 'options' => [ 38 | 'width' => 60 39 | ] 40 | ], 41 | [ 42 | 'attribute' => 'last_visit_at', 43 | 'format' => ['date', 'Y-m-d H:i:s'], 44 | 'options' => [ 45 | 'width' => 140 46 | ] 47 | ], 48 | [ 49 | 'attribute' => 'created_at', 50 | 'format' => ['date', 'Y-m-d H:i:s'], 51 | 'options' => [ 52 | 'width' => 140 53 | ] 54 | ], 55 | [ 56 | 'attribute' => 'updated_at', 57 | 'format' => ['date', 'Y-m-d H:i:s'], 58 | 'options' => [ 59 | 'width' => 140 60 | ] 61 | ], 62 | 63 | ] 64 | ]) ?> 65 |
66 |
-------------------------------------------------------------------------------- /modules/admin/widgets/ActiveForm.php: -------------------------------------------------------------------------------- 1 | [ 12 | 'class' => 'col-sm-3 control-label' 13 | ], 14 | 'template' => "
{label}\n
{input}\n{error}
\n
{hint}
" 15 | ]; 16 | public $options = [ 17 | 'class' => 'form-horizontal' 18 | ]; 19 | 20 | /** 21 | * 表单动作模板 22 | * @var string 23 | */ 24 | public $actionTemplate = "
{action}
"; 25 | 26 | /** 27 | * 表单动作渲染 28 | * @param $action 29 | * @return string 30 | */ 31 | public function action($action) 32 | { 33 | return strtr($this->actionTemplate, [ 34 | '{action}' => $action 35 | ]); 36 | } 37 | } -------------------------------------------------------------------------------- /modules/admin/widgets/GridView.php: -------------------------------------------------------------------------------- 1 | button]]. 18 | * @inheritdoc 19 | */ 20 | public $layout = "
{pager}\n{summary}\n{button}\n
{items}
"; 21 | /** 22 | * @inheritdoc 23 | */ 24 | public $summaryOptions = [ 25 | 'class' => 'pull-left summary' 26 | ]; 27 | /** 28 | * @inheritdoc 29 | */ 30 | public $pager = [ 31 | 'options' => [ 32 | 'class' => 'pull-left pagination' 33 | ] 34 | ]; 35 | 36 | /** 37 | * @inheritdoc 38 | */ 39 | public function renderSection($name) 40 | { 41 | switch ($name) { 42 | case '{button}': 43 | return $this->button; 44 | default: 45 | return parent::renderSection($name); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /modules/forum/Module.php: -------------------------------------------------------------------------------- 1 | [ 26 | 'class' => VerbFilter::className(), 27 | 'actions' => [ 28 | 'delete' => ['post'], 29 | ], 30 | ], 31 | 'access' => [ 32 | 'class' => AccessControl::className(), 33 | 'rules' => [ 34 | // 默认只能Get方式访问 35 | [ 36 | 'allow' => true, 37 | 'actions' => ['view'], 38 | 'verbs' => ['GET'], 39 | ], 40 | // 登录用户才能提交评论或其他内容 41 | [ 42 | 'allow' => true, 43 | 'actions' => ['view'], 44 | 'verbs' => ['POST'], 45 | 'roles' => ['@'], 46 | ], 47 | // 登录用户才能使用API操作(赞,踩,收藏) 48 | [ 49 | 'allow' => true, 50 | 'actions' => ['api', 'test'], 51 | 'roles' => ['@'] 52 | ], 53 | ] 54 | ] 55 | ]; 56 | } 57 | 58 | /** 59 | * Displays a single Topic model. 60 | * @param integer $id 61 | * @return mixed 62 | */ 63 | public function actionView($id) 64 | { 65 | $model = $this->findModel($id, 'topic', function ($model) { 66 | $model->active(); 67 | }); 68 | $request = Yii::$app->request; 69 | $commentDataProvider = (new CommentSearch())->search($request->queryParams, $model->getComments()); 70 | $commentDataProvider->query->with(['hate', 'like', 'favorite', 'author', 'author.avatar'])->active(); 71 | return $this->render('view', [ 72 | 'model' => $model, 73 | 'comment' => $this->newComment($model), 74 | 'commentDataProvider' => $commentDataProvider 75 | ]); 76 | } 77 | 78 | /** 79 | * 收藏, 赞, 踩, 标签 接口 80 | * @param $id 81 | */ 82 | public function actionApi() 83 | { 84 | $request = Yii::$app->request; 85 | $model = $this->findModel($request->post('id'), $request->post('type'), function ($model) { 86 | $model->active(); 87 | }); 88 | $opeartions = ['favorite', 'like', 'hate']; 89 | if (!in_array($do = $request->post('do'), $opeartions)) { 90 | return $this->message('错误的操作', 'error'); 91 | } 92 | $result = $model->{'toggle' . $do}(Yii::$app->user->getId()); 93 | if ($result !== true) { 94 | return $this->message($result === false ? '操作失败' : $result, 'error'); 95 | } 96 | return $this->message('操作成功', 'success'); 97 | } 98 | 99 | public function actionTest($id) 100 | { 101 | $model = $this->findModel($id); 102 | $model->toggleLike(Yii::$app->user->getId()); 103 | return $this->render('test', [ 104 | 'model' => $model 105 | ]); 106 | } 107 | 108 | /** 109 | * Finds the Topic model based on its primary key value. 110 | * If the model is not found, a 404 HTTP exception will be thrown. 111 | * @param integer $id 112 | * @return Topic the loaded model 113 | * @throws NotFoundHttpException if the model cannot be found 114 | */ 115 | protected function findModel($id, $type = 'topic', \Closure $func = null) 116 | { 117 | if ($id) { 118 | $model = $type == 'topic' ? Topic::find() : Comment::find(); 119 | $model->andWhere(['id' => $id])->active(); 120 | $func !== null && $func($model); 121 | $model = $model->one(); 122 | if ($model !== null) { 123 | return $model; 124 | } 125 | } 126 | throw new NotFoundHttpException('The requested page does not exist.'); 127 | } 128 | 129 | /** 130 | * 创建新评论 131 | * @param $topic 132 | * @return Comment 133 | */ 134 | protected function newComment(Topic $topic) 135 | { 136 | $model = new Comment; 137 | if ($model->load(Yii::$app->request->post())) { 138 | $model->author_id = Yii::$app->user->id; 139 | if ($topic->addComment($model, true)) { 140 | $this->flash('发表评论成功!', 'success'); 141 | Yii::$app->end(0, $this->refresh()); 142 | } 143 | } 144 | return $model; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /modules/forum/models/Comment.php: -------------------------------------------------------------------------------- 1 | andWhere(['>', 'tid', 0]); 39 | } 40 | 41 | public function rules() 42 | { 43 | return [ 44 | [['fid', 'tid', 'content', 'author_id'], 'required'], 45 | [['subject'], 'default', 'value' => ''], 46 | [['status'], 'in', 'range' => [static::STATUS_ACTIVE, static::STATUS_AUDIT, static::STATUS_DELETED]] 47 | ]; 48 | } 49 | 50 | /** 51 | * 获取话题 52 | */ 53 | public function getTopic() 54 | { 55 | return $this->hasOne(Topic::className(), ['id' => 'tid']); 56 | } 57 | } -------------------------------------------------------------------------------- /modules/forum/models/CommentQuery.php: -------------------------------------------------------------------------------- 1 | andWhere(['status' => Comment::STATUS_ACTIVE]); 11 | return $this; 12 | } 13 | } -------------------------------------------------------------------------------- /modules/forum/models/CommentSearch.php: -------------------------------------------------------------------------------- 1 | $query, 47 | ]); 48 | 49 | if (!($this->load($params) && $this->validate())) { 50 | return $dataProvider; 51 | } 52 | 53 | $query->andFilterWhere([ 54 | 'id' => $this->id, 55 | 'fid' => $this->fid, 56 | 'tid' => $this->tid, 57 | 'author_id' => $this->author_id, 58 | 'view_count' => $this->view_count, 59 | 'comment_count' => $this->comment_count, 60 | 'like_count' => $this->like_count, 61 | 'created_at' => $this->created_at, 62 | 'updated_at' => $this->updated_at, 63 | ]); 64 | 65 | $query->andFilterWhere(['like', 'subject', $this->subject]) 66 | ->andFilterWhere(['like', 'content', $this->content]); 67 | 68 | return $dataProvider; 69 | } 70 | } -------------------------------------------------------------------------------- /modules/forum/models/Forum.php: -------------------------------------------------------------------------------- 1 | ''] 18 | ]; 19 | } 20 | 21 | public function behaviors() 22 | { 23 | return [ 24 | 'timestamp' => [ 25 | 'class' => 'yii\behaviors\TimestampBehavior', 26 | ], 27 | ]; 28 | } 29 | 30 | public function getTopics() 31 | { 32 | return $this->hasMany(Topic::className(), ['fid' => 'id']); 33 | } 34 | 35 | /** 36 | * 发表新帖 37 | * @param Topic $topic 38 | * @param bool $active 激活 39 | * @return bool 40 | */ 41 | public function addTopic(Topic $topic, $active = false) 42 | { 43 | $topic->setAttributes([ 44 | 'fid' => $this->id, 45 | ]); 46 | $result = $topic->save(); 47 | if ($result) { 48 | $active && $topic->setActive(); 49 | return true; 50 | } 51 | return false; 52 | } 53 | } -------------------------------------------------------------------------------- /modules/forum/models/ForumSearch.php: -------------------------------------------------------------------------------- 1 | $query, 48 | ]); 49 | 50 | if (!($this->load($params) && $this->validate())) { 51 | return $dataProvider; 52 | } 53 | 54 | 55 | 56 | $query->andFilterWhere([ 57 | 'id' => $this->id, 58 | 'parent' => $this->parent, 59 | 'topic_count' => $this->topic_count, 60 | ]) 61 | ->andFilterWhere(['like', 'name', $this->name]) 62 | ->andFilterWhere(['like', 'description', $this->description]) 63 | ->andFilterWhere(['like', 'icon', $this->icon]) 64 | ->orderBy($order); 65 | return $dataProvider; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /modules/forum/models/Topic.php: -------------------------------------------------------------------------------- 1 | where(['tid' => 0]); 35 | } 36 | 37 | public function rules() 38 | { 39 | return [ 40 | [['fid', 'subject', 'content', 'author_id'], 'required'], 41 | [['status'], 'in', 'range' => [static::STATUS_ACTIVE, static::STATUS_AUDIT, static::STATUS_DELETED]] 42 | ]; 43 | } 44 | 45 | /** 46 | * 获取评论列表 47 | * @return ActiveQuery 48 | */ 49 | public function getComments() 50 | { 51 | return $this->hasMany(Comment::className(), ['tid' => 'id']); 52 | } 53 | 54 | /** 55 | * 添加评论 56 | * @param Comment $comment 57 | * @param bool $active 激活 58 | * @return bool 59 | */ 60 | public function addComment(Comment $comment, $active = false) 61 | { 62 | $comment->setAttributes([ 63 | 'tid' => $this->id, 64 | 'fid' => $this->fid 65 | ]); 66 | $result = $comment->save(); 67 | if ($result) { 68 | $active && $comment->setActive(); 69 | return true; 70 | } 71 | return false; 72 | } 73 | 74 | /** 75 | * 获取帖子标签(关联tag_item表) 76 | * @return mixed 77 | */ 78 | public function getTags() 79 | { 80 | return $this->hasMany(Tag::className(), ['id' => 'tid']) 81 | ->viaTable(TagItem::tableName(), ['target_id' => 'id'], function($model) { 82 | $model->andWhere(['target_type' => static::TYPE]); 83 | }); 84 | } 85 | 86 | /** 87 | * 获取帖子标签记录 88 | * @return mixed 89 | */ 90 | public function getTagItems() 91 | { 92 | return $this->hasMany(TagItem::className(), ['target_id' => 'id']) 93 | ->andWhere(['target_type' => static::TYPE]); 94 | } 95 | 96 | /** 97 | * 添加标签 98 | * @param array $tags 99 | * @return bool 100 | */ 101 | public function addTags(array $tags) 102 | { 103 | $return = false; 104 | $tagItem = new TagItem(); 105 | foreach ($tags as $tag) { 106 | $_tagItem = clone $tagItem; 107 | $_tagItem->setAttributes([ 108 | 'tid' => $tag->id, 109 | 'target_id' => $this->id, 110 | 'target_type' => static::TYPE 111 | ]); 112 | if ($_tagItem->save() && $return == false) { 113 | $return = true; 114 | } 115 | } 116 | return $return; 117 | } 118 | } -------------------------------------------------------------------------------- /modules/forum/models/TopicQuery.php: -------------------------------------------------------------------------------- 1 | andWhere(['status' => Topic::STATUS_ACTIVE]); 11 | return $this; 12 | } 13 | } -------------------------------------------------------------------------------- /modules/forum/models/TopicSearch.php: -------------------------------------------------------------------------------- 1 | $query, 48 | 'sort' => [ 49 | 'defaultOrder' => [ 50 | 'created_at' => SORT_DESC 51 | ], 52 | 'attributes' => ['created_at'] 53 | ] 54 | ]); 55 | 56 | if (!($this->load($params) && $this->validate())) { 57 | return $dataProvider; 58 | } 59 | 60 | $query->andFilterWhere([ 61 | 'id' => $this->id, 62 | 'fid' => $this->fid, 63 | 'comment_count' => $this->comment_count, 64 | 'created_at' => $this->created_at, 65 | 'updated_at' => $this->updated_at, 66 | ]); 67 | 68 | $query->andFilterWhere(['like', 'subject', $this->subject]) 69 | ->andFilterWhere(['like', 'content', $this->content]); 70 | 71 | return $dataProvider; 72 | } 73 | 74 | public function searchWithComments($params) 75 | { 76 | if (!isset($params[$this->formName()]['id'])) { 77 | throw new InvalidParamException('必须填写话题ID'); 78 | } 79 | $dataProvider = $this->search($params, false); 80 | $dataProvider->query->orWhere(['fid' => $this->id]); 81 | return $dataProvider; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /modules/forum/views/default/_form.php: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 | 14 | 15 | field($model, 'id')->textInput() ?> 16 | 17 | field($model, 'parent')->textInput() ?> 18 | 19 | field($model, 'name')->textInput(['maxlength' => 255]) ?> 20 | 21 | field($model, 'description')->textarea(['rows' => 6]) ?> 22 | 23 | field($model, 'icon')->textInput(['maxlength' => 255]) ?> 24 | 25 | field($model, 'topic_count')->textInput() ?> 26 | 27 |
28 | isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> 29 |
30 | 31 | 32 | 33 |
34 | -------------------------------------------------------------------------------- /modules/forum/views/default/_search.php: -------------------------------------------------------------------------------- 1 | 10 | 11 | 38 | -------------------------------------------------------------------------------- /modules/forum/views/default/_topic.php: -------------------------------------------------------------------------------- 1 | 4 |
5 |
6 |
7 |
8 | 9 | comment_count ?> 10 | 11 | 12 | 16 |
17 |
18 | author->getAvatarUrl(), ['class' => 'avatar']), ['topic/view', 'id' => $model->id], [ 19 | 'data-toggle' => 'tooltip', 20 | 'data-placement' => 'bottom', 21 | 'title' => Html::encode($model->author->username), 22 | 'class' => 'pull-right' 23 | ]) ?> 24 |
subject), ['topic/view', 'id' => $model->id]) ?>
25 |
26 | view_count ?>  27 | tags  28 | updated_at) ?> 29 |
30 |
31 |
32 |
33 |
34 | -------------------------------------------------------------------------------- /modules/forum/views/default/_topicForm.php: -------------------------------------------------------------------------------- 1 | 12 |
13 | [ 15 | 'template' => "{input}\n{hint}\n{error}" 16 | ] 17 | ]); ?> 18 | errorSummary($model, [ 19 | 'class' => 'alert alert-danger' 20 | ]) ?> 21 | field($model, 'subject')->textInput([ 22 | 'class' => 'form-control input-lg', 23 | 'placeholder' => '话题' 24 | ]) ?> 25 |
26 | 'topicTags', 28 | 'placeholder' => '请点击选择标签', 29 | ]) ?> 30 |
31 |
32 |
33 | field($model, 'content', [ 34 | 'selectors' => [ 35 | 'input' => '#wmd-input' 36 | ], 37 | ])->textarea([ 38 | 'id' => 'wmd-input', 39 | 'class' => 'form-control input-lg wmd-input', 40 | 'placeholder' => '内容', 41 | 'rows' => 10 42 | ]) ?> 43 |
44 |
45 | isNewRecord ? '我要发布' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success btn-lg' : 'btn btn-primary btn-lg']) ?> 46 |
47 |
48 | 49 |
50 | '{name}', 'do' => 'search']); 52 | $tagCreateApiUrl = Url::to(['/tag/create']); 53 | $script = <<' + 71 | (item.icon ? '' : '') + 72 | '' + escape(item.name) + '' + 73 | ''; 74 | } 75 | }, 76 | load: function(query, callback) { 77 | query = $.trim(query); 78 | if (!query.length) return callback(); 79 | $.ajax({ 80 | url: ('{$tagSearchApiUrl}').replace(encodeURIComponent('{name}'), encodeURIComponent(query)), 81 | type: 'GET', 82 | error: function() { 83 | callback(); 84 | }, 85 | success: function(res) { 86 | res.type == 'success' ? callback(res.message) : callback(); 87 | } 88 | }); 89 | }, 90 | onOptionAdd: function (value, data) { 91 | if (!data.hasOwnProperty('id')) { 92 | var _this = this; 93 | $.ajax({ 94 | url: ('{$tagCreateApiUrl}'), 95 | type: 'POST', 96 | data: { 97 | Tag: { 98 | name: value 99 | } 100 | }, 101 | success: function(res) { 102 | if (res.type != 'success') { 103 | alert(res.message); 104 | if (res.type == 'error') { 105 | _this.removeOption(value); 106 | } 107 | } 108 | } 109 | }); 110 | } 111 | } 112 | }); 113 | EOF; 114 | $this->registerJs($script); -------------------------------------------------------------------------------- /modules/forum/views/default/create.php: -------------------------------------------------------------------------------- 1 | title = 'Create Forum'; 10 | $this->params['breadcrumbs'][] = ['label' => 'Forums', 'url' => ['index']]; 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 |
14 | 15 |

title) ?>

16 | 17 | render('_form', [ 18 | 'model' => $model, 19 | ]) ?> 20 | 21 |
22 | -------------------------------------------------------------------------------- /modules/forum/views/default/index.php: -------------------------------------------------------------------------------- 1 | title = 'Forums'; 12 | $this->params['breadcrumbs'][] = $this->title; 13 | 14 | ?> 15 |
16 | 17 |

title) ?>

18 | render('_search', ['model' => $searchModel]); ?> 19 | 20 |

21 | 'btn btn-success']) ?> 22 |

23 | 24 | $dataProvider, 26 | 'itemOptions' => ['class' => 'item'], 27 | 'itemView' => function ($model, $key, $index, $widget) { 28 | return Html::a(Html::encode($model->name), ['view', 'id' => $model->id]); 29 | }, 30 | ]) ?> 31 | 32 |
33 | -------------------------------------------------------------------------------- /modules/forum/views/default/post.php: -------------------------------------------------------------------------------- 1 | title = '发表话题'; 11 | $this->params['breadcrumbs'][] = ['label' => 'Forums', 'url' => ['index']]; 12 | $this->params['breadcrumbs'][] = $this->title; 13 | ?> 14 |
15 |

title) ?>

16 | render('_topicForm', [ 17 | 'model' => $topic, 18 | ]) ?> 19 |
20 | -------------------------------------------------------------------------------- /modules/forum/views/default/update.php: -------------------------------------------------------------------------------- 1 | title = 'Update Forum: ' . ' ' . $model->name; 9 | $this->params['breadcrumbs'][] = ['label' => 'Forums', 'url' => ['index']]; 10 | $this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['view', 'id' => $model->id]]; 11 | $this->params['breadcrumbs'][] = 'Update'; 12 | ?> 13 |
14 | 15 |

title) ?>

16 | 17 | render('_form', [ 18 | 'model' => $model, 19 | ]) ?> 20 | 21 |
22 | -------------------------------------------------------------------------------- /modules/forum/views/default/view.php: -------------------------------------------------------------------------------- 1 | title = $model ? $model->name : '最新提问'; 16 | $this->params['breadcrumbs'][] = ['label' => 'Forums', 'url' => ['index']]; 17 | $this->params['breadcrumbs'][] = $this->title; 18 | $baseUrl = $model ? ['', 'id' => $model->id] : ['']; 19 | ?> 20 |
21 |

title) ?>

22 | 23 |

description) ?>

24 |

25 | 说点什么把? 26 | 我要发表 27 |

28 | 29 | 35 | $topicDataProvider, 37 | 'itemOptions' => ['class' => 'item'], 38 | 'summary' => false, 39 | 'itemView' => '_topic', 40 | ]) ?> 41 |
42 | -------------------------------------------------------------------------------- /modules/forum/views/topic/_commentForm.php: -------------------------------------------------------------------------------- 1 | registerJs(" 7 | $('#wmd-input').one('click', function(){ 8 | var commentConverter = Markdown.getSanitizingConverter(); 9 | commentEditor = new Markdown.Editor(commentConverter); 10 | commentEditor.run(); 11 | }); 12 | "); 13 | ?> 14 |
15 |

发表评论

16 | [ 18 | 'template' => "{input}\n{hint}\n{error}" 19 | ] 20 | ]); ?> 21 | errorSummary($model, [ 22 | 'class' => 'alert alert-danger' 23 | ]) ?> 24 |
25 |
26 | field($model, 'content', [ 27 | 'selectors' => [ 28 | 'input' => '#wmd-input' 29 | ], 30 | ])->textarea([ 31 | 'id' => 'wmd-input', 32 | 'class' => 'form-control input-lg wmd-input', 33 | 'rows' => 6 34 | ]) ?> 35 |
36 |
37 | 'btn btn-success']) ?> 38 |
39 |
40 | 41 |
42 | -------------------------------------------------------------------------------- /modules/forum/views/topic/_search.php: -------------------------------------------------------------------------------- 1 | 10 | 11 | 40 | -------------------------------------------------------------------------------- /modules/forum/views/topic/test.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /modules/forum/views/topic/view.php: -------------------------------------------------------------------------------- 1 | title = $model->id; 11 | $this->params['breadcrumbs'][] = ['label' => 'Topics', 'url' => ['index']]; 12 | $this->params['breadcrumbs'][] = $this->title; 13 | ?> 14 |
15 | render('_topic', [ 16 | 'model' => $model 17 | ]) ?> 18 | $commentDataProvider, 20 | 'itemView' => '_topic', 21 | 'summary' => false, 22 | 'emptyText' => '暂时还没有新的评论', 23 | 'emptyTextOptions' => [ 24 | 'class' => 'text-center' 25 | ] 26 | ]) ?> 27 | user->getIsGuest()): ?> 28 | render('_commentForm', [ 29 | 'model' => $comment 30 | ]) ?> 31 | 32 |
-------------------------------------------------------------------------------- /modules/user/Module.php: -------------------------------------------------------------------------------- 1 | 9 | * @since 2.0 10 | */ 11 | class UserAsset extends AssetBundle 12 | { 13 | public $sourcePath = '@app/modules/user/assets/web'; 14 | public $css = [ 15 | 'css/user.css', 16 | ]; 17 | public $js = [ 18 | //'js/user.js', 19 | ]; 20 | public $depends = [ 21 | 'app\assets\AppAsset', 22 | ]; 23 | } 24 | -------------------------------------------------------------------------------- /modules/user/assets/web/css/user.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yii2-chinesization/yii-qa/131211ae7643b3787cc1870488c03bc9def8e6de/modules/user/assets/web/css/user.css -------------------------------------------------------------------------------- /modules/user/components/User.php: -------------------------------------------------------------------------------- 1 | identity; 19 | $identity->setAttribute('last_visit_at', TIMESTAMP); 20 | // $identity->setAttribute('last_login_ip', ip2long(Yii::$app->getRequest()->getUserIP())); 21 | $identity->setAttribute('last_login_ip', Yii::$app->getRequest()->getUserIP()); 22 | $identity->save(false); 23 | 24 | parent::afterLogin($identity, $cookieBased, $duration); 25 | } 26 | } -------------------------------------------------------------------------------- /modules/user/components/authclient/clients/QQ.php: -------------------------------------------------------------------------------- 1 | api('user/get_user_info.' . $this->format, 'GET'); 29 | } 30 | 31 | /** 32 | * @inheritdoc 33 | */ 34 | protected function defaultName() 35 | { 36 | return 'qq'; 37 | } 38 | 39 | /** 40 | * @inheritdoc 41 | */ 42 | protected function defaultTitle() 43 | { 44 | return '腾讯QQ'; 45 | } 46 | } -------------------------------------------------------------------------------- /modules/user/components/authclient/clients/WeiBo.php: -------------------------------------------------------------------------------- 1 | api('account/get_uid.' . $this->format, 'GET'); 29 | } 30 | 31 | /** 32 | * @inheritdoc 33 | */ 34 | protected function defaultName() 35 | { 36 | return 'weibo'; 37 | } 38 | 39 | /** 40 | * @inheritdoc 41 | */ 42 | protected function defaultTitle() 43 | { 44 | return '新浪微博'; 45 | } 46 | } -------------------------------------------------------------------------------- /modules/user/controllers/DefaultController.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'class' => AccessControl::className(), 18 | 'rules' => [ 19 | [ 20 | 'actions' => ['login', 'register', 'auth'], 21 | 'allow' => true, 22 | 'roles' => ['?'], 23 | ], 24 | [ 25 | 'actions' => ['logout'], 26 | 'allow' => true, 27 | 'roles' => ['@'], 28 | ], 29 | ], 30 | ], 31 | ]; 32 | } 33 | 34 | public function actions() 35 | { 36 | return [ 37 | 'auth' => [ 38 | 'class' => 'yii\authclient\AuthAction', 39 | 'successCallback' => [$this, 'successCallback'], 40 | ], 41 | ]; 42 | } 43 | 44 | public function successCallback($client) 45 | { 46 | $attributes = $client->getUserAttributes(); 47 | // user login or signup comes here 48 | var_dump($client); 49 | var_dump($attributes); 50 | exit; 51 | } 52 | 53 | public function actionIndex() 54 | { 55 | return $this->render('index'); 56 | } 57 | 58 | public function actionLogin() 59 | { 60 | if (!Yii::$app->user->isGuest) { 61 | return $this->goHome(); 62 | } 63 | 64 | $loginForm = new LoginForm(); 65 | if ($loginForm->load(Yii::$app->request->post()) && $loginForm->login()) { 66 | return $this->goBack(); 67 | } else { 68 | $method = Yii::$app->getRequest()->isAjax ? 'renderAjax' : 'render'; 69 | return $this->$method('login', [ 70 | 'loginForm' => $loginForm, 71 | ]); 72 | } 73 | } 74 | 75 | public function actionRegister() 76 | { 77 | $registerForm = new RegisterForm(); 78 | if ($registerForm->load(Yii::$app->request->post())) { 79 | $user = $registerForm->register(); 80 | if ($user) { 81 | if (Yii::$app->getUser()->login($user)) { 82 | return $this->goHome(); 83 | } 84 | } 85 | } 86 | $method = Yii::$app->getRequest()->isAjax ? 'renderAjax' : 'render'; 87 | return $this->$method('login', [ 88 | 'registerForm' => $registerForm, 89 | ]); 90 | } 91 | 92 | public function actionLogout() 93 | { 94 | Yii::$app->getUser()->logout(); 95 | 96 | return $this->goHome(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /modules/user/controllers/HomeController.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'class' => AccessControl::className(), 17 | 'rules' => [ 18 | [ 19 | 'actions' => ['index'], 20 | 'allow' => true, 21 | ], 22 | ], 23 | ], 24 | ]; 25 | } 26 | 27 | public function actionIndex($id) 28 | { 29 | return $this->render('index', [ 30 | 'model' => $this->findModel($id) 31 | ]); 32 | } 33 | 34 | protected function findModel($id) 35 | { 36 | if (($model = Yii::$app->user->id == $id ? Yii::$app->user->identity : User::findOne($id)) !== null) { 37 | return $model; 38 | } else { 39 | throw new NotFoundHttpException('The requested page does not exist.'); 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /modules/user/controllers/admin/UserController.php: -------------------------------------------------------------------------------- 1 | [ 21 | 'class' => VerbFilter::className(), 22 | 'actions' => [ 23 | 'delete' => ['post'], 24 | ], 25 | ], 26 | ]; 27 | } 28 | 29 | /** 30 | * Lists all User models. 31 | * @return mixed 32 | */ 33 | public function actionIndex() 34 | { 35 | $searchModel = new UserAdmin(); 36 | $dataProvider = $searchModel->search(Yii::$app->request->queryParams); 37 | 38 | return $this->render('index', [ 39 | 'searchModel' => $searchModel, 40 | 'dataProvider' => $dataProvider, 41 | ]); 42 | } 43 | 44 | /** 45 | * Displays a single User model. 46 | * @param integer $id 47 | * @return mixed 48 | */ 49 | public function actionView($id) 50 | { 51 | return $this->render('view', [ 52 | 'model' => $this->findModel($id), 53 | ]); 54 | } 55 | 56 | /** 57 | * Creates a new User model. 58 | * If creation is successful, the browser will be redirected to the 'view' page. 59 | * @return mixed 60 | */ 61 | public function actionCreate() 62 | { 63 | $model = new User(); 64 | 65 | if ($model->load(Yii::$app->request->post()) && $model->save()) { 66 | return $this->redirect(['view', 'id' => $model->id]); 67 | } else { 68 | return $this->render('create', [ 69 | 'model' => $model, 70 | ]); 71 | } 72 | } 73 | 74 | /** 75 | * Updates an existing User model. 76 | * If update is successful, the browser will be redirected to the 'view' page. 77 | * @param integer $id 78 | * @return mixed 79 | */ 80 | public function actionUpdate($id) 81 | { 82 | $model = $this->findModel($id); 83 | 84 | if ($model->load(Yii::$app->request->post()) && $model->save()) { 85 | return $this->redirect(['view', 'id' => $model->id]); 86 | } else { 87 | return $this->render('update', [ 88 | 'model' => $model, 89 | ]); 90 | } 91 | } 92 | 93 | /** 94 | * Deletes an existing User model. 95 | * If deletion is successful, the browser will be redirected to the 'index' page. 96 | * @param integer $id 97 | * @return mixed 98 | */ 99 | public function actionDelete($id) 100 | { 101 | $this->findModel($id)->delete(); 102 | 103 | return $this->redirect(['index']); 104 | } 105 | 106 | /** 107 | * Finds the User model based on its primary key value. 108 | * If the model is not found, a 404 HTTP exception will be thrown. 109 | * @param integer $id 110 | * @return User the loaded model 111 | * @throws NotFoundHttpException if the model cannot be found 112 | */ 113 | protected function findModel($id) 114 | { 115 | if (($model = User::findOne($id)) !== null) { 116 | return $model; 117 | } else { 118 | throw new NotFoundHttpException('The requested page does not exist.'); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /modules/user/helpers/UserAvatarHelper.php: -------------------------------------------------------------------------------- 1 | ['author_id', 'path'], 31 | ]; 32 | } 33 | 34 | public function beforeSave($insert) 35 | { 36 | if ($insert) { 37 | $this->status = self::STATUS_ACTIVE; // 默认上传的头像通过验证 38 | } 39 | return parent::beforeSave($insert); 40 | } 41 | 42 | public function behaviors() 43 | { 44 | return [ 45 | 'timestamp' => [ 46 | 'class' => 'yii\behaviors\TimestampBehavior', 47 | 'attributes' => [ 48 | ActiveRecord::EVENT_BEFORE_INSERT => 'created_at', 49 | ], 50 | ], 51 | ]; 52 | } 53 | 54 | public function getStorage() 55 | { 56 | return $this->hasOne(Storage::className(), ['sid' => 'id']); 57 | } 58 | 59 | /** 60 | * 获取头像Url 61 | * @return string 62 | */ 63 | public function getUrl() 64 | { 65 | return $this->storage->getUrl() ? : static::getOrginalUrl(); 66 | } 67 | 68 | /** 69 | * 获取原始的头像url 70 | * @return string 71 | */ 72 | public static function getDefaultUrl() 73 | { 74 | return Url::to(['/images/anonymous.jpg']); 75 | } 76 | } -------------------------------------------------------------------------------- /modules/user/models/Favorite.php: -------------------------------------------------------------------------------- 1 | '用户名', 38 | 'password' => '密码', 39 | 'rememberMe' => '记住我' 40 | ]; 41 | } 42 | 43 | /** 44 | * Validates the password. 45 | * This method serves as the inline validation for password. 46 | */ 47 | public function validatePassword() 48 | { 49 | if (!$this->hasErrors()) { 50 | $user = $this->getUser(); 51 | 52 | if (!$user || !$user->validatePassword($this->password)) { 53 | $this->addError('password', 'Incorrect username or password.'); 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * Logs in a user using the provided username and password. 60 | * @return boolean whether the user is logged in successfully 61 | */ 62 | public function login() 63 | { 64 | if ($this->validate()) { 65 | return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0); 66 | } else { 67 | return false; 68 | } 69 | } 70 | 71 | /** 72 | * Finds user by [[username]] 73 | * 74 | * @return User|null 75 | */ 76 | public function getUser() 77 | { 78 | if ($this->_user === false) { 79 | $this->_user = User::findByUsername($this->username); 80 | } 81 | return $this->_user; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /modules/user/models/Meta.php: -------------------------------------------------------------------------------- 1 | static::TYPE], 22 | [['value'], 'default', 'value' => 1], 23 | [['uid', 'target_type', 'type', 'value'], 'required'] 24 | ]; 25 | } 26 | 27 | public static function find() 28 | { 29 | return parent::find()->where(['type' => static::TYPE]); 30 | } 31 | 32 | public function behaviors() 33 | { 34 | return [ 35 | 'timestamp' => [ 36 | 'class' => 'yii\behaviors\TimestampBehavior', 37 | 'attributes' => [ 38 | ActiveRecord::EVENT_BEFORE_INSERT => 'created_at', 39 | ], 40 | ], 41 | ]; 42 | } 43 | } -------------------------------------------------------------------------------- /modules/user/models/Profile.php: -------------------------------------------------------------------------------- 1 | 'trim'], 23 | ['username', 'required'], 24 | ['username', 'unique', 'targetClass' => '\app\modules\user\models\User', 'message' => 'This username has already been taken.'], 25 | ['username', 'string', 'min' => 2, 'max' => 255], 26 | 27 | ['email', 'filter', 'filter' => 'trim'], 28 | ['email', 'required'], 29 | ['email', 'email'], 30 | ['email', 'unique', 'targetClass' => '\app\modules\user\models\User', 'message' => 'This email address has already been taken.'], 31 | 32 | ['password', 'required'], 33 | ['password', 'string', 'min' => 5], 34 | ]; 35 | } 36 | 37 | public function attributeLabels() 38 | { 39 | return [ 40 | 'username' => '用户名', 41 | 'email' => '邮箱', 42 | 'password' => '密码' 43 | ]; 44 | } 45 | 46 | /** 47 | * user register. 48 | * 49 | * @return User|null the saved model or null if saving fails 50 | */ 51 | public function register() 52 | { 53 | if ($this->validate()) { 54 | return User::create($this->attributes); 55 | } 56 | 57 | return null; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /modules/user/models/UserAdmin.php: -------------------------------------------------------------------------------- 1 | $query, 48 | ]); 49 | 50 | if (!($this->load($params) && $this->validate())) { 51 | return $dataProvider; 52 | } 53 | 54 | $query->andFilterWhere([ 55 | 'id' => $this->id, 56 | 'followers' => $this->followers, 57 | 'following' => $this->following, 58 | 'photos' => $this->photos, 59 | 'avatar_sid' => $this->avatar_sid, 60 | 'status' => $this->status, 61 | 'last_visit_at' => $this->last_visit_at, 62 | 'created_at' => $this->created_at, 63 | 'updated_at' => $this->updated_at, 64 | ]); 65 | 66 | $query->andFilterWhere(['like', 'email', $this->email]) 67 | ->andFilterWhere(['like', 'username', $this->username]) 68 | ->andFilterWhere(['like', 'auth_key', $this->auth_key]) 69 | ->andFilterWhere(['like', 'password_hash', $this->password_hash]) 70 | ->andFilterWhere(['like', 'password_reset_token', $this->password_reset_token]) 71 | ->andFilterWhere(['like', 'last_login_ip', $this->last_login_ip]); 72 | 73 | return $dataProvider; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /modules/user/models/UserAvatar.php: -------------------------------------------------------------------------------- 1 | ['author_id', 'path'], 26 | ]; 27 | } 28 | 29 | public function beforeSave($insert) 30 | { 31 | if ($insert) { 32 | $this->status = self::STATUS_ACTIVE; // 默认上传的头像通过验证 33 | } 34 | return parent::beforeSave($insert); 35 | } 36 | 37 | public function behaviors() 38 | { 39 | return [ 40 | 'timestamp' => [ 41 | 'class' => 'yii\behaviors\TimestampBehavior', 42 | 'attributes' => [ 43 | ActiveRecord::EVENT_BEFORE_INSERT => 'created_at', 44 | ], 45 | ], 46 | ]; 47 | } 48 | 49 | public function getStorage() 50 | { 51 | return $this->hasOne(Storage::className(), ['sid' => 'id']); 52 | } 53 | 54 | /** 55 | * 获取头像Url 56 | * @return string 57 | */ 58 | public function getUrl() 59 | { 60 | return $this->storage->getUrl() ? : static::getOrginalUrl(); 61 | } 62 | 63 | /** 64 | * 获取原始的头像url 65 | * @return string 66 | */ 67 | public static function getDefaultUrl() 68 | { 69 | return Url::to(['/images/anonymous.jpg']); 70 | } 71 | } -------------------------------------------------------------------------------- /modules/user/views/admin/rbac/index.php: -------------------------------------------------------------------------------- 1 | 123 -------------------------------------------------------------------------------- /modules/user/views/admin/rbac/permissions.php: -------------------------------------------------------------------------------- 1 | 6 |
7 |
8 | $permissionsProvider, 10 | 'button' => Html::a('添加权限', ['add-permission'], ['class' => 'btn btn-primary pull-right']), 11 | 'columns' => [ 12 | [ 13 | 'attribute' => 'name', 14 | 'label' => '角色名', 15 | 'format' => 'html', 16 | 'value' => function ($data) { 17 | return Html::a($data->name, ['update-permission', 'name' => $data->name]); 18 | } 19 | ], 20 | [ 21 | 'attribute' => 'description', 22 | 'label' => $authItemForm->getAttributeLabel('description'), 23 | ], 24 | [ 25 | 'attribute' => 'ruleName', 26 | 'label' => $authItemForm->getAttributeLabel('ruleName'), 27 | ], 28 | [ 29 | 'attribute' => 'data', 30 | 'label' => $authItemForm->getAttributeLabel('data'), 31 | ], 32 | [ 33 | 'attribute' => 'createdAt', 34 | 'label' => $authItemForm->getAttributeLabel('createdAt'), 35 | 'format' => ['date', 'Y-m-d H:i:s'], 36 | 'options' => [ 37 | 'width' => 140 38 | ] 39 | ], 40 | [ 41 | 'attribute' => 'updatedAt', 42 | 'label' => $authItemForm->getAttributeLabel('updatedAt'), 43 | 'format' => ['date', 'Y-m-d H:i:s'], 44 | 'options' => [ 45 | 'width' => 140 46 | ] 47 | ], 48 | ] 49 | ]) ?> 50 |
51 |
-------------------------------------------------------------------------------- /modules/user/views/admin/rbac/roles.php: -------------------------------------------------------------------------------- 1 | title = '角色列表'; 6 | $this->params['breadcrumbs'] = [ 7 | [ 8 | 'url' => ['/admin/rbac'], 9 | 'label' => '角色与权限' 10 | ], 11 | $this->title 12 | ]; 13 | ?> 14 |
15 |
16 | $rolesProvider, 19 | 'button' => Html::a('添加角色', ['add-role'], ['class' => 'btn btn-primary pull-right']), 20 | 'columns' => [ 21 | [ 22 | 'attribute' => 'name', 23 | 'label' => $authItemForm->getAttributeLabel('name'), 24 | 'format' => 'html', 25 | 'value' => function ($data) { 26 | return Html::a($data->name, ['update-role', 'name' => $data->name]); 27 | } 28 | ], 29 | [ 30 | 'attribute' => 'description', 31 | 'label' => $authItemForm->getAttributeLabel('description'), 32 | ], 33 | [ 34 | 'attribute' => 'ruleName', 35 | 'label' => $authItemForm->getAttributeLabel('ruleName'), 36 | ], 37 | [ 38 | 'attribute' => 'data', 39 | 'label' => $authItemForm->getAttributeLabel('data'), 40 | ], 41 | [ 42 | 'attribute' => 'createdAt', 43 | 'label' => $authItemForm->getAttributeLabel('createdAt'), 44 | 'format' => ['date', 'Y-m-d H:i:s'], 45 | 'options' => [ 46 | 'width' => 140 47 | ] 48 | ], 49 | [ 50 | 'attribute' => 'updatedAt', 51 | 'label' => $authItemForm->getAttributeLabel('updatedAt'), 52 | 'format' => ['date', 'Y-m-d H:i:s'], 53 | 'options' => [ 54 | 'width' => 140 55 | ] 56 | ], 57 | ] 58 | ]) ?> 59 |
60 |
-------------------------------------------------------------------------------- /modules/user/views/admin/user/_form.php: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 | 14 | 15 | field($model, 'email')->textInput(['maxlength' => 40]) ?> 16 | 17 | field($model, 'username')->textInput(['maxlength' => 20]) ?> 18 | 19 | field($model, 'status')->textInput() ?> 20 | 21 |
22 | isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> 23 |
24 | 25 | 26 | 27 |
28 | -------------------------------------------------------------------------------- /modules/user/views/admin/user/_search.php: -------------------------------------------------------------------------------- 1 | 10 | 11 | 56 | -------------------------------------------------------------------------------- /modules/user/views/admin/user/create.php: -------------------------------------------------------------------------------- 1 | title = 'Create User'; 10 | $this->params['breadcrumbs'][] = ['label' => 'Users', 'url' => ['index']]; 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 |
14 | 15 |

title) ?>

16 | 17 | render('_form', [ 18 | 'model' => $model, 19 | ]) ?> 20 | 21 |
22 | -------------------------------------------------------------------------------- /modules/user/views/admin/user/index.php: -------------------------------------------------------------------------------- 1 | title = 'Users'; 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 |
14 | 15 |

title) ?>

16 | render('_search', ['model' => $searchModel]); ?> 17 | 18 |

19 | 'btn btn-success']) ?> 20 |

21 | 22 | $dataProvider, 24 | 'filterModel' => $searchModel, 25 | 'columns' => [ 26 | ['class' => 'yii\grid\SerialColumn'], 27 | 28 | 'id', 29 | 'email:email', 30 | 'username', 31 | 'auth_key', 32 | 'password_hash', 33 | // 'password_reset_token', 34 | // 'followers', 35 | // 'following', 36 | // 'photos', 37 | // 'avatar_sid', 38 | // 'status', 39 | // 'last_login_ip', 40 | // 'last_visit_at', 41 | // 'created_at', 42 | // 'updated_at', 43 | 44 | ['class' => 'yii\grid\ActionColumn'], 45 | ], 46 | ]); ?> 47 | 48 |
49 | -------------------------------------------------------------------------------- /modules/user/views/admin/user/update.php: -------------------------------------------------------------------------------- 1 | title = 'Update User: ' . ' ' . $model->id; 9 | $this->params['breadcrumbs'][] = ['label' => 'Users', 'url' => ['index']]; 10 | $this->params['breadcrumbs'][] = ['label' => $model->id, 'url' => ['view', 'id' => $model->id]]; 11 | $this->params['breadcrumbs'][] = 'Update'; 12 | ?> 13 |
14 | 15 |

title) ?>

16 | 17 | render('_form', [ 18 | 'model' => $model, 19 | ]) ?> 20 | 21 |
22 | -------------------------------------------------------------------------------- /modules/user/views/admin/user/view.php: -------------------------------------------------------------------------------- 1 | title = $model->id; 10 | $this->params['breadcrumbs'][] = ['label' => 'Users', 'url' => ['index']]; 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 |
14 | 15 |

title) ?>

16 | 17 |

18 | $model->id], ['class' => 'btn btn-primary']) ?> 19 | $model->id], [ 20 | 'class' => 'btn btn-danger', 21 | 'data' => [ 22 | 'confirm' => 'Are you sure you want to delete this item?', 23 | 'method' => 'post', 24 | ], 25 | ]) ?> 26 |

27 | 28 | $model, 30 | 'attributes' => [ 31 | 'id', 32 | 'email:email', 33 | 'username', 34 | 'auth_key', 35 | 'password_hash', 36 | 'password_reset_token', 37 | 'followers', 38 | 'following', 39 | 'photos', 40 | 'avatar_sid', 41 | 'status', 42 | 'last_login_ip', 43 | 'last_visit_at', 44 | 'created_at', 45 | 'updated_at', 46 | ], 47 | ]) ?> 48 | 49 |
50 | -------------------------------------------------------------------------------- /modules/user/views/default/index.php: -------------------------------------------------------------------------------- 1 |
2 |

context->action->uniqueId ?>

3 | 4 |

5 | This is the view content for action "context->action->id ?>". 6 | The action belongs to the controller "context) ?>" 7 | in the "context->module->id ?>" module. 8 |

9 | 10 |

11 | You may customize this page by editing the following file:
12 | 13 |

14 |
15 | -------------------------------------------------------------------------------- /modules/user/views/default/login.php: -------------------------------------------------------------------------------- 1 | title = $isLogin ? '登录' : '注册'; 11 | $this->params['header'] = false; 12 | UserAsset::register($this); 13 | $user = Yii::$app->getUser(); 14 | AjaxModal::begin(); 15 | ?> 16 | 59 | getUser()->getIdentity(); 8 | $this->title = $identity->username; 9 | ?> 10 |
11 |

username) ?> @username) ?>

12 | 20 |
21 |
22 | 23 | getAvatarUrl()) ?> 24 | 25 |
26 |
27 | 28 | getAvatarUrl()) ?> 29 | 30 |
31 |
32 |
33 |
34 |
35 |
36 | getAvatarUrl()) ?> 37 |
38 |
39 | 点击修改头像 40 | 41 |
42 |
43 | 48 |
    49 |
  • 50 | 51 | email ?> 52 |
  • 53 |
  • 54 | 55 | created_at) ?> 56 | 57 |
  • 58 |
59 |
60 |
61 | 62 |
63 |
64 |
-------------------------------------------------------------------------------- /requirements.php: -------------------------------------------------------------------------------- 1 | Error'; 18 | echo '

The path to yii framework seems to be incorrect.

'; 19 | echo '

You need to install Yii framework via composer or adjust the framework path in file ' . basename(__FILE__) . '.

'; 20 | echo '

Please refer to the README on how to install Yii.

'; 21 | } 22 | 23 | require_once($frameworkPath . '/requirements/YiiRequirementChecker.php'); 24 | $requirementsChecker = new YiiRequirementChecker(); 25 | 26 | /** 27 | * Adjust requirements according to your application specifics. 28 | */ 29 | $requirements = array( 30 | // Database : 31 | array( 32 | 'name' => 'PDO extension', 33 | 'mandatory' => true, 34 | 'condition' => extension_loaded('pdo'), 35 | 'by' => 'All DB-related classes', 36 | ), 37 | array( 38 | 'name' => 'PDO SQLite extension', 39 | 'mandatory' => false, 40 | 'condition' => extension_loaded('pdo_sqlite'), 41 | 'by' => 'All DB-related classes', 42 | 'memo' => 'Required for SQLite database.', 43 | ), 44 | array( 45 | 'name' => 'PDO MySQL extension', 46 | 'mandatory' => false, 47 | 'condition' => extension_loaded('pdo_mysql'), 48 | 'by' => 'All DB-related classes', 49 | 'memo' => 'Required for MySQL database.', 50 | ), 51 | array( 52 | 'name' => 'PDO PostgreSQL extension', 53 | 'mandatory' => false, 54 | 'condition' => extension_loaded('pdo_pgsql'), 55 | 'by' => 'All DB-related classes', 56 | 'memo' => 'Required for PostgreSQL database.', 57 | ), 58 | // Cache : 59 | array( 60 | 'name' => 'Memcache extension', 61 | 'mandatory' => false, 62 | 'condition' => extension_loaded('memcache') || extension_loaded('memcached'), 63 | 'by' => 'CMemCache', 64 | 'memo' => extension_loaded('memcached') ? 'To use memcached set CMemCache::useMemcached to true.' : '' 65 | ), 66 | array( 67 | 'name' => 'APC extension', 68 | 'mandatory' => false, 69 | 'condition' => extension_loaded('apc'), 70 | 'by' => 'CApcCache', 71 | ), 72 | // Additional PHP extensions : 73 | array( 74 | 'name' => 'Mcrypt extension', 75 | 'mandatory' => false, 76 | 'condition' => extension_loaded('mcrypt'), 77 | 'by' => 'CSecurityManager', 78 | 'memo' => 'Required by encrypt and decrypt methods.' 79 | ), 80 | // PHP ini : 81 | 'phpSafeMode' => array( 82 | 'name' => 'PHP safe mode', 83 | 'mandatory' => false, 84 | 'condition' => $requirementsChecker->checkPhpIniOff("safe_mode"), 85 | 'by' => 'File uploading and console command execution', 86 | 'memo' => '"safe_mode" should be disabled at php.ini', 87 | ), 88 | 'phpExposePhp' => array( 89 | 'name' => 'Expose PHP', 90 | 'mandatory' => false, 91 | 'condition' => $requirementsChecker->checkPhpIniOff("expose_php"), 92 | 'by' => 'Security reasons', 93 | 'memo' => '"expose_php" should be disabled at php.ini', 94 | ), 95 | 'phpAllowUrlInclude' => array( 96 | 'name' => 'PHP allow url include', 97 | 'mandatory' => false, 98 | 'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"), 99 | 'by' => 'Security reasons', 100 | 'memo' => '"allow_url_include" should be disabled at php.ini', 101 | ), 102 | 'phpSmtp' => array( 103 | 'name' => 'PHP mail SMTP', 104 | 'mandatory' => false, 105 | 'condition' => strlen(ini_get('SMTP'))>0, 106 | 'by' => 'Email sending', 107 | 'memo' => 'PHP mail SMTP server required', 108 | ), 109 | ); 110 | $requirementsChecker->checkYii()->check($requirements)->render(); 111 | -------------------------------------------------------------------------------- /runtime/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | # these files are auto generated by codeception build 2 | /unit/CodeGuy.php 3 | /functional/TestGuy.php 4 | /acceptance/WebGuy.php 5 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | This folder contains various tests for the basic application. 2 | These tests are developed with [Codeception PHP Testing Framework](http://codeception.com/). 3 | 4 | After creating the basic application, follow these steps to prepare for the tests: 5 | 6 | 1. Install additional composer packages: 7 | 8 | ``` 9 | php composer.phar require --dev "codeception/codeception: 1.8.*@dev" "codeception/specify: *" "codeception/verify: *" 10 | ``` 11 | 2. In the file `_bootstrap.php`, modify the definition of the constant `TEST_ENTRY_URL` so 12 | that it points to the correct entry script URL. 13 | 3. Go to the application base directory and build the test suites: 14 | 15 | ``` 16 | vendor/bin/codecept build 17 | ``` 18 | 19 | Now you can run the tests with the following commands: 20 | 21 | ``` 22 | # run all available tests 23 | vendor/bin/codecept run 24 | # run acceptance tests 25 | vendor/bin/codecept run acceptance 26 | # run functional tests 27 | vendor/bin/codecept run functional 28 | # run unit tests 29 | vendor/bin/codecept run unit 30 | ``` 31 | 32 | Please refer to [Codeception tutorial](http://codeception.com/docs/01-Introduction) for 33 | more details about writing and running acceptance, functional and unit tests. 34 | -------------------------------------------------------------------------------- /tests/_bootstrap.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'mail' => [ 8 | 'useFileTransport' => true, 9 | ], 10 | 'urlManager' => [ 11 | 'showScriptName' => true, 12 | ], 13 | ], 14 | ]; 15 | -------------------------------------------------------------------------------- /tests/_console_bootstrap.php: -------------------------------------------------------------------------------- 1 | $value) { 17 | $inputType = $field === 'body' ? 'textarea' : 'input'; 18 | $this->guy->fillField($inputType . '[name="ContactForm[' . $field . ']"]', $value); 19 | } 20 | $this->guy->click('contact-button'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/_pages/LoginPage.php: -------------------------------------------------------------------------------- 1 | guy->fillField('input[name="LoginForm[username]"]', $username); 18 | $this->guy->fillField('input[name="LoginForm[password]"]', $password); 19 | $this->guy->click('login-button'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/acceptance.suite.yml: -------------------------------------------------------------------------------- 1 | # Codeception Test Suite Configuration 2 | 3 | # suite for acceptance tests. 4 | # perform tests in browser using the Selenium-like tools. 5 | # powered by Mink (http://mink.behat.org). 6 | # (tip: that's what your customer will see). 7 | # (tip: test your ajax and javascript by one of Mink drivers). 8 | 9 | # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES. 10 | 11 | class_name: WebGuy 12 | modules: 13 | enabled: 14 | - WebHelper 15 | - PhpBrowser 16 | # you can use WebDriver instead of PhpBrowser to test javascript and ajax. 17 | # This will require you to install selenium. See http://codeception.com/docs/04-AcceptanceTests#Selenium 18 | # "restart" option is used by the WebDriver to start each time per test-file new session and cookies, 19 | # it is useful if you want to login in your app in each test. 20 | # - WebDriver 21 | config: 22 | PhpBrowser: 23 | url: 'http://localhost:8080' 24 | # WebDriver: 25 | # url: 'http://localhost' 26 | # browser: firefox 27 | # restart: true 28 | -------------------------------------------------------------------------------- /tests/acceptance/AboutCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that about works'); 7 | AboutPage::openBy($I); 8 | $I->see('About', 'h1'); 9 | -------------------------------------------------------------------------------- /tests/acceptance/ContactCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that contact works'); 7 | 8 | $contactPage = ContactPage::openBy($I); 9 | 10 | $I->see('Contact', 'h1'); 11 | 12 | $I->amGoingTo('submit contact form with no data'); 13 | $contactPage->submit([]); 14 | $I->expectTo('see validations errors'); 15 | $I->see('Contact', 'h1'); 16 | $I->see('Name cannot be blank'); 17 | $I->see('Email cannot be blank'); 18 | $I->see('Subject cannot be blank'); 19 | $I->see('Body cannot be blank'); 20 | $I->see('The verification code is incorrect'); 21 | 22 | $I->amGoingTo('submit contact form with not correct email'); 23 | $contactPage->submit([ 24 | 'name' => 'tester', 25 | 'email' => 'tester.email', 26 | 'subject' => 'test subject', 27 | 'body' => 'test content', 28 | 'verifyCode' => 'testme', 29 | ]); 30 | $I->expectTo('see that email adress is wrong'); 31 | $I->dontSee('Name cannot be blank', '.help-inline'); 32 | $I->see('Email is not a valid email address.'); 33 | $I->dontSee('Subject cannot be blank', '.help-inline'); 34 | $I->dontSee('Body cannot be blank', '.help-inline'); 35 | $I->dontSee('The verification code is incorrect', '.help-inline'); 36 | 37 | $I->amGoingTo('submit contact form with correct data'); 38 | $contactPage->submit([ 39 | 'name' => 'tester', 40 | 'email' => 'tester@example.com', 41 | 'subject' => 'test subject', 42 | 'body' => 'test content', 43 | 'verifyCode' => 'testme', 44 | ]); 45 | if (method_exists($I, 'wait')) { 46 | $I->wait(3); // only for selenium 47 | } 48 | $I->dontSeeElement('#contact-form'); 49 | $I->see('Thank you for contacting us. We will respond to you as soon as possible.'); 50 | -------------------------------------------------------------------------------- /tests/acceptance/HomeCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that home page works'); 5 | $I->amOnPage(Yii::$app->homeUrl); 6 | $I->see('My Company'); 7 | $I->seeLink('About'); 8 | $I->click('About'); 9 | $I->see('This is the About page.'); 10 | -------------------------------------------------------------------------------- /tests/acceptance/LoginCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that login works'); 7 | 8 | $loginPage = LoginPage::openBy($I); 9 | 10 | $I->see('Login', 'h1'); 11 | 12 | $I->amGoingTo('try to login with empty credentials'); 13 | $loginPage->login('', ''); 14 | $I->expectTo('see validations errors'); 15 | $I->see('Username cannot be blank.'); 16 | $I->see('Password cannot be blank.'); 17 | 18 | $I->amGoingTo('try to login with wrong credentials'); 19 | $loginPage->login('admin', 'wrong'); 20 | $I->expectTo('see validations errors'); 21 | $I->see('Incorrect username or password.'); 22 | 23 | $I->amGoingTo('try to login with correct credentials'); 24 | $loginPage->login('admin', 'admin'); 25 | if (method_exists($I, 'wait')) { 26 | $I->wait(3); // only for selenium 27 | } 28 | $I->expectTo('see user info'); 29 | $I->see('Logout (admin)'); 30 | -------------------------------------------------------------------------------- /tests/acceptance/_bootstrap.php: -------------------------------------------------------------------------------- 1 | [ 8 | 'db' => [ 9 | 'dsn' => 'mysql:host=localhost;dbname=yii2_basic_acceptance', 10 | ], 11 | ], 12 | ] 13 | ); 14 | -------------------------------------------------------------------------------- /tests/acceptance/_console.php: -------------------------------------------------------------------------------- 1 | [ 8 | 'db' => [ 9 | 'dsn' => 'mysql:host=localhost;dbname=yii2_basic_acceptance', 10 | ], 11 | ], 12 | ] 13 | ); 14 | -------------------------------------------------------------------------------- /tests/acceptance/yii: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 17 | exit($exitCode); 18 | -------------------------------------------------------------------------------- /tests/acceptance/yii.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Yii command line bootstrap script for Windows. 5 | rem 6 | rem @author Qiang Xue 7 | rem @link http://www.yiiframework.com/ 8 | rem @copyright Copyright © 2012 Yii Software LLC 9 | rem @license http://www.yiiframework.com/license/ 10 | rem ------------------------------------------------------------- 11 | 12 | @setlocal 13 | 14 | set YII_PATH=%~dp0 15 | 16 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 17 | 18 | "%PHP_COMMAND%" "%YII_PATH%yii" %* 19 | 20 | @endlocal 21 | -------------------------------------------------------------------------------- /tests/functional.suite.yml: -------------------------------------------------------------------------------- 1 | # Codeception Test Suite Configuration 2 | 3 | # suite for functional (integration) tests. 4 | # emulate web requests and make application process them. 5 | # (tip: better to use with frameworks). 6 | 7 | # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES. 8 | #basic/web/index.php 9 | class_name: TestGuy 10 | modules: 11 | enabled: 12 | - Filesystem 13 | - TestHelper 14 | - Yii2 15 | config: 16 | Yii2: 17 | configFile: 'tests/functional/_config.php' 18 | -------------------------------------------------------------------------------- /tests/functional/AboutCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that about works'); 7 | AboutPage::openBy($I); 8 | $I->see('About', 'h1'); 9 | -------------------------------------------------------------------------------- /tests/functional/ContactCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that contact works'); 7 | 8 | $contactPage = ContactPage::openBy($I); 9 | 10 | $I->see('Contact', 'h1'); 11 | 12 | $I->amGoingTo('submit contact form with no data'); 13 | $contactPage->submit([]); 14 | $I->expectTo('see validations errors'); 15 | $I->see('Contact', 'h1'); 16 | $I->see('Name cannot be blank'); 17 | $I->see('Email cannot be blank'); 18 | $I->see('Subject cannot be blank'); 19 | $I->see('Body cannot be blank'); 20 | $I->see('The verification code is incorrect'); 21 | 22 | $I->amGoingTo('submit contact form with not correct email'); 23 | $contactPage->submit([ 24 | 'name' => 'tester', 25 | 'email' => 'tester.email', 26 | 'subject' => 'test subject', 27 | 'body' => 'test content', 28 | 'verifyCode' => 'testme', 29 | ]); 30 | $I->expectTo('see that email adress is wrong'); 31 | $I->dontSee('Name cannot be blank', '.help-inline'); 32 | $I->see('Email is not a valid email address.'); 33 | $I->dontSee('Subject cannot be blank', '.help-inline'); 34 | $I->dontSee('Body cannot be blank', '.help-inline'); 35 | $I->dontSee('The verification code is incorrect', '.help-inline'); 36 | 37 | $I->amGoingTo('submit contact form with correct data'); 38 | $contactPage->submit([ 39 | 'name' => 'tester', 40 | 'email' => 'tester@example.com', 41 | 'subject' => 'test subject', 42 | 'body' => 'test content', 43 | 'verifyCode' => 'testme', 44 | ]); 45 | $I->dontSeeElement('#contact-form'); 46 | $I->see('Thank you for contacting us. We will respond to you as soon as possible.'); 47 | -------------------------------------------------------------------------------- /tests/functional/HomeCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that home page works'); 5 | $I->amOnPage(Yii::$app->homeUrl); 6 | $I->see('My Company'); 7 | $I->seeLink('About'); 8 | $I->click('About'); 9 | $I->see('This is the About page.'); 10 | -------------------------------------------------------------------------------- /tests/functional/LoginCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that login works'); 7 | 8 | $loginPage = LoginPage::openBy($I); 9 | 10 | $I->see('Login', 'h1'); 11 | 12 | $I->amGoingTo('try to login with empty credentials'); 13 | $loginPage->login('', ''); 14 | $I->expectTo('see validations errors'); 15 | $I->see('Username cannot be blank.'); 16 | $I->see('Password cannot be blank.'); 17 | 18 | $I->amGoingTo('try to login with wrong credentials'); 19 | $loginPage->login('admin', 'wrong'); 20 | $I->expectTo('see validations errors'); 21 | $I->see('Incorrect username or password.'); 22 | 23 | $I->amGoingTo('try to login with correct credentials'); 24 | $loginPage->login('admin', 'admin'); 25 | $I->expectTo('see user info'); 26 | $I->see('Logout (admin)'); 27 | -------------------------------------------------------------------------------- /tests/functional/_bootstrap.php: -------------------------------------------------------------------------------- 1 | [ 12 | 'db' => [ 13 | 'dsn' => 'mysql:host=localhost;dbname=yii2_basic_functional', 14 | ], 15 | ], 16 | ] 17 | ); 18 | -------------------------------------------------------------------------------- /tests/functional/_console.php: -------------------------------------------------------------------------------- 1 | [ 8 | 'db' => [ 9 | 'dsn' => 'mysql:host=localhost;dbname=yii2_basic_functional', 10 | ], 11 | ], 12 | ] 13 | ); 14 | -------------------------------------------------------------------------------- /tests/functional/yii: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 17 | exit($exitCode); 18 | -------------------------------------------------------------------------------- /tests/functional/yii.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Yii command line bootstrap script for Windows. 5 | rem 6 | rem @author Qiang Xue 7 | rem @link http://www.yiiframework.com/ 8 | rem @copyright Copyright © 2012 Yii Software LLC 9 | rem @license http://www.yiiframework.com/license/ 10 | rem ------------------------------------------------------------- 11 | 12 | @setlocal 13 | 14 | set YII_PATH=%~dp0 15 | 16 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 17 | 18 | "%PHP_COMMAND%" "%YII_PATH%yii" %* 19 | 20 | @endlocal 21 | -------------------------------------------------------------------------------- /tests/unit.suite.yml: -------------------------------------------------------------------------------- 1 | # Codeception Test Suite Configuration 2 | 3 | # suite for unit (internal) tests. 4 | # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES. 5 | 6 | class_name: CodeGuy 7 | modules: 8 | enabled: 9 | - CodeHelper 10 | -------------------------------------------------------------------------------- /tests/unit/_bootstrap.php: -------------------------------------------------------------------------------- 1 | [ 8 | 'db' => [ 9 | 'dsn' => 'mysql:host=localhost;dbname=yii2_basic_unit', 10 | ], 11 | ], 12 | ] 13 | ); 14 | -------------------------------------------------------------------------------- /tests/unit/_console.php: -------------------------------------------------------------------------------- 1 | [ 8 | 'db' => [ 9 | 'dsn' => 'mysql:host=localhost;dbname=yii2_basic_unit', 10 | ], 11 | ], 12 | ] 13 | ); 14 | -------------------------------------------------------------------------------- /tests/unit/fixtures/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yii2-chinesization/yii-qa/131211ae7643b3787cc1870488c03bc9def8e6de/tests/unit/fixtures/.gitkeep -------------------------------------------------------------------------------- /tests/unit/models/ContactFormTest.php: -------------------------------------------------------------------------------- 1 | mail->fileTransportCallback = function ($mailer, $message) { 16 | return 'testing_message.eml'; 17 | }; 18 | } 19 | 20 | protected function tearDown() 21 | { 22 | unlink($this->getMessageFile()); 23 | parent::tearDown(); 24 | } 25 | 26 | public function testContact() 27 | { 28 | $model = $this->getMock('app\models\ContactForm', ['validate']); 29 | $model->expects($this->once())->method('validate')->will($this->returnValue(true)); 30 | 31 | $model->attributes = [ 32 | 'name' => 'Tester', 33 | 'email' => 'tester@example.com', 34 | 'subject' => 'very important letter subject', 35 | 'body' => 'body of current message', 36 | ]; 37 | 38 | $model->contact('admin@example.com'); 39 | 40 | $this->specify('email should be send', function () { 41 | expect('email file should exist', file_exists($this->getMessageFile()))->true(); 42 | }); 43 | 44 | $this->specify('message should contain correct data', function () use ($model) { 45 | $emailMessage = file_get_contents($this->getMessageFile()); 46 | 47 | expect('email should contain user name', $emailMessage)->contains($model->name); 48 | expect('email should contain sender email', $emailMessage)->contains($model->email); 49 | expect('email should contain subject', $emailMessage)->contains($model->subject); 50 | expect('email should contain body', $emailMessage)->contains($model->body); 51 | }); 52 | } 53 | 54 | private function getMessageFile() 55 | { 56 | return Yii::getAlias(Yii::$app->mail->fileTransportPath) . '/testing_message.eml'; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/unit/models/LoginFormTest.php: -------------------------------------------------------------------------------- 1 | user->logout(); 16 | parent::tearDown(); 17 | } 18 | 19 | public function testLoginNoUser() 20 | { 21 | $model = $this->mockUser(null); 22 | 23 | $model->username = 'some_username'; 24 | $model->password = 'some_password'; 25 | 26 | $this->specify('user should not be able to login, when there is no identity', function () use ($model) { 27 | expect('model should not login user', $model->login())->false(); 28 | expect('user should not be logged in', Yii::$app->user->isGuest)->true(); 29 | }); 30 | } 31 | 32 | public function testLoginWrongPassword() 33 | { 34 | $model = $this->mockUser(new User); 35 | 36 | $model->username = 'demo'; 37 | $model->password = 'wrong-password'; 38 | 39 | $this->specify('user should not be able to login with wrong password', function () use ($model) { 40 | expect('model should not login user', $model->login())->false(); 41 | expect('error message should be set', $model->errors)->hasKey('password'); 42 | expect('user should not be logged in', Yii::$app->user->isGuest)->true(); 43 | }); 44 | } 45 | 46 | public function testLoginCorrect() 47 | { 48 | $model = $this->mockUser(new User(['password' => 'demo'])); 49 | 50 | $model->username = 'demo'; 51 | $model->password = 'demo'; 52 | 53 | $this->specify('user should be able to login with correct credentials', function () use ($model) { 54 | expect('model should login user', $model->login())->true(); 55 | expect('error message should not be set', $model->errors)->hasntKey('password'); 56 | expect('user should be logged in', Yii::$app->user->isGuest)->false(); 57 | }); 58 | } 59 | 60 | private function mockUser($user) 61 | { 62 | $loginForm = $this->getMock('app\models\LoginForm', ['getUser']); 63 | $loginForm->expects($this->any())->method('getUser')->will($this->returnValue($user)); 64 | return $loginForm; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/unit/models/UserTest.php: -------------------------------------------------------------------------------- 1 | loadFixtures(['tbl_user']); 14 | } 15 | 16 | // TODO add test methods here 17 | } 18 | -------------------------------------------------------------------------------- /tests/unit/templates/fixtures/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yii2-chinesization/yii-qa/131211ae7643b3787cc1870488c03bc9def8e6de/tests/unit/templates/fixtures/.gitkeep -------------------------------------------------------------------------------- /tests/unit/yii: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 17 | exit($exitCode); 18 | -------------------------------------------------------------------------------- /tests/unit/yii.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Yii command line bootstrap script for Windows. 5 | rem 6 | rem @author Qiang Xue 7 | rem @link http://www.yiiframework.com/ 8 | rem @copyright Copyright © 2012 Yii Software LLC 9 | rem @license http://www.yiiframework.com/license/ 10 | rem ------------------------------------------------------------- 11 | 12 | @setlocal 13 | 14 | set YII_PATH=%~dp0 15 | 16 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 17 | 18 | "%PHP_COMMAND%" "%YII_PATH%yii" %* 19 | 20 | @endlocal 21 | -------------------------------------------------------------------------------- /vendor/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /views/common/message.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |

' . print_r($message) . '' : $message) ?>

4 |
5 |
-------------------------------------------------------------------------------- /views/layouts/main.php: -------------------------------------------------------------------------------- 1 | 10 | beginPage() ?> 11 | 12 | 13 | 14 | 15 | 16 | 17 | <?= Html::encode($this->title) ?> - <?= Yii::$app->name ?> 18 | head() ?> 19 | 20 | 21 | beginBody() ?> 22 | params['container']) ? $this->params['container'] : null ?> 23 | 24 | 25 | 26 | Yii::$app->name, 29 | 'brandUrl' => ['/user/home/index', 'id' => Yii::$app->user->id], 30 | 'options' => [ 31 | 'class' => 'navbar-white', 32 | ], 33 | 'innerContainerOptions' => [ 34 | 'class' => 'container container-narrow' 35 | ] 36 | ]); 37 | echo TopMenu::widget(); 38 | NavBar::end(); 39 | ?> 40 |
41 | 42 | 43 |
44 | 45 |
46 |
47 |

Powered by CallMeZ

48 |
49 |
50 | 51 | 52 | 53 | endBody() ?> 54 | 55 | 56 | endPage() ?> 57 | -------------------------------------------------------------------------------- /views/site/about.php: -------------------------------------------------------------------------------- 1 | title = 'About'; 8 | $this->params['breadcrumbs'][] = $this->title; 9 | ?> 10 |
11 |

title) ?>

12 | 13 |

14 | This is the About page. You may modify the following file to customize its content: 15 |

16 | 17 | 18 |
19 | -------------------------------------------------------------------------------- /views/site/contact.php: -------------------------------------------------------------------------------- 1 | title = 'Contact'; 12 | $this->params['breadcrumbs'][] = $this->title; 13 | ?> 14 |
15 |

title) ?>

16 | 17 | session->hasFlash('contactFormSubmitted')): ?> 18 | 19 |
20 | Thank you for contacting us. We will respond to you as soon as possible. 21 |
22 | 23 |

24 | Note that if you turn on the Yii debugger, you should be able 25 | to view the mail message on the mail panel of the debugger. 26 | mail->useFileTransport): ?> 27 | Because the application is in development mode, the email is not sent but saved as 28 | a file under mail->fileTransportPath) ?>. 29 | Please configure the useFileTransport property of the mail 30 | application component to be false to enable email sending. 31 | 32 |

33 | 34 | 35 | 36 |

37 | If you have business inquiries or other questions, please fill out the following form to contact us. Thank you. 38 |

39 | 40 |
41 |
42 | 'contact-form']); ?> 43 | field($model, 'name') ?> 44 | field($model, 'email') ?> 45 | field($model, 'subject') ?> 46 | field($model, 'body')->textArea(['rows' => 6]) ?> 47 | field($model, 'verifyCode')->widget(Captcha::className(), [ 48 | 'template' => '
{image}
{input}
', 49 | ]) ?> 50 |
51 | 'btn btn-primary', 'name' => 'contact-button']) ?> 52 |
53 | 54 |
55 |
56 | 57 | 58 |
59 | -------------------------------------------------------------------------------- /views/site/error.php: -------------------------------------------------------------------------------- 1 | title = $name; 13 | ?> 14 |
15 | 16 |

title) ?>

17 | 18 |
19 | 20 |
21 | 22 |

23 | The above error occurred while the Web server was processing your request. 24 |

25 |

26 | Please contact us if you think this is a server error. Thank you. 27 |

28 | 29 |
30 | -------------------------------------------------------------------------------- /views/site/index.php: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /views/site/login.php: -------------------------------------------------------------------------------- 1 | title = 'Login'; 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 | 48 | -------------------------------------------------------------------------------- /views/site/test.php: -------------------------------------------------------------------------------- 1 | get('storageCollection'); 3 | $storageCollection->getStorage('qiniu')->registerUploadJs([ 4 | 'uploadSettings' => [ 5 | 'autoUpload' => true 6 | ], 7 | ]); 8 | ?> 9 |
10 | 11 | 12 |
-------------------------------------------------------------------------------- /views/tag/_form.php: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 | 14 | 15 | field($model, 'name')->textInput(['maxlength' => 64]) ?> 16 | 17 |
18 | isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> 19 |
20 | 21 | 22 | 23 |
24 | -------------------------------------------------------------------------------- /views/tag/_search.php: -------------------------------------------------------------------------------- 1 | 10 | 11 | 40 | -------------------------------------------------------------------------------- /views/tag/create.php: -------------------------------------------------------------------------------- 1 | title = 'Create Tag'; 10 | $this->params['breadcrumbs'][] = ['label' => 'Tags', 'url' => ['index']]; 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 |
14 | 15 |

title) ?>

16 | 17 | render('_form', [ 18 | 'model' => $model, 19 | ]) ?> 20 | 21 |
22 | -------------------------------------------------------------------------------- /views/tag/index.php: -------------------------------------------------------------------------------- 1 | title = 'Tags'; 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 |
14 | 15 |

title) ?>

16 | render('_search', ['model' => $searchModel]); ?> 17 | 18 |

19 | 'btn btn-success']) ?> 20 |

21 | 22 | $dataProvider, 24 | 'itemOptions' => ['class' => 'item'], 25 | 'itemView' => function ($model, $key, $index, $widget) { 26 | return Html::a(Html::encode($model->name), ['view', 'id' => $model->id]); 27 | }, 28 | ]) ?> 29 | 30 |
31 | -------------------------------------------------------------------------------- /views/tag/update.php: -------------------------------------------------------------------------------- 1 | title = 'Update Tag: ' . ' ' . $model->name; 9 | $this->params['breadcrumbs'][] = ['label' => 'Tags', 'url' => ['index']]; 10 | $this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['view', 'id' => $model->id]]; 11 | $this->params['breadcrumbs'][] = 'Update'; 12 | ?> 13 |
14 | 15 |

title) ?>

16 | 17 | render('_form', [ 18 | 'model' => $model, 19 | ]) ?> 20 | 21 |
22 | -------------------------------------------------------------------------------- /views/tag/view.php: -------------------------------------------------------------------------------- 1 | title = $model->name; 10 | $this->params['breadcrumbs'][] = ['label' => 'Tags', 'url' => ['index']]; 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 |
14 | 15 |

title) ?>

16 | 17 |

18 | $model->id], ['class' => 'btn btn-primary']) ?> 19 | $model->id], [ 20 | 'class' => 'btn btn-danger', 21 | 'data' => [ 22 | 'confirm' => 'Are you sure you want to delete this item?', 23 | 'method' => 'post', 24 | ], 25 | ]) ?> 26 |

27 | 28 | $model, 30 | 'attributes' => [ 31 | 'id', 32 | 'name', 33 | 'icon', 34 | 'description:ntext', 35 | 'status', 36 | 'created_at', 37 | 'updated_at', 38 | ], 39 | ]) ?> 40 | 41 |
42 | -------------------------------------------------------------------------------- /web/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine on 2 | 3 | # If a directory or a file exists, use it directly 4 | RewriteCond %{REQUEST_FILENAME} !-f 5 | RewriteCond %{REQUEST_FILENAME} !-d 6 | # Otherwise forward it to index.php 7 | RewriteRule . index.php 8 | 9 | #七牛云存储的回调会传送HTTP_AUTHORIZATION认证秘钥,一定要放在最rewrite的后面.防止影响 10 | #PHP在CGI模式下的认证信息的获取 11 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] -------------------------------------------------------------------------------- /web/assets/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /web/css/global.css: -------------------------------------------------------------------------------- 1 | /* Reset */ 2 | canvas { 3 | vertical-align: middle; 4 | } 5 | /* Global */ 6 | .m0 { 7 | margin: 0; 8 | } 9 | .mt0 { 10 | margin-top: 0; 11 | } 12 | .mb0 { 13 | margin-bottom: 0; 14 | } 15 | .mb10 { 16 | margin-bottom: 10px; 17 | } 18 | .mb15 { 19 | margin-bottom: 15px; 20 | } 21 | .p10 { 22 | padding: 10px; 23 | } 24 | .box { 25 | border: 1px solid #EEE; 26 | padding: 15px; 27 | } 28 | /* Bootstrap */ 29 | .navbar-white { 30 | border-bottom-color: #EEE; 31 | background-color: #FFF; 32 | border-radius: 0; 33 | } 34 | .navbar-white .navbar-toggle { 35 | border-color: #EEE; 36 | } 37 | .navbar-white .navbar-toggle .icon-bar { 38 | background-color: #EEE; 39 | } 40 | .navbar-white .navbar-toggle:hover, 41 | .navbar-white .navbar-toggle:focus { 42 | border-color: #CCC; 43 | } 44 | .navbar-white .navbar-toggle:hover .icon-bar, 45 | .navbar-white .navbar-toggle:focus .icon-bar { 46 | background-color: #CCC; 47 | } 48 | .container-narrow { 49 | max-width: 720px; 50 | } 51 | .navbar .avatar-xs { 52 | margin: -5px 5px -5px 0; 53 | border-radius: 3px; 54 | } 55 | .text-muted { 56 | color: #999; 57 | } 58 | /* Jquery File Upload */ 59 | .file-input { 60 | position: relative; 61 | overflow: hidden; 62 | } 63 | .file-input input { 64 | position: absolute; 65 | top: 0; 66 | right: 0; 67 | margin: 0; 68 | opacity: 0; 69 | -ms-filter: 'alpha(opacity=0)'; 70 | font-size: 200px; 71 | direction: ltr; 72 | cursor: pointer; 73 | } 74 | /* Fixes for IE < 8 */ 75 | @media screen\9 { 76 | .file-input input { 77 | filter: alpha(opacity=0); 78 | font-size: 100%; 79 | height: 100%; 80 | } 81 | } -------------------------------------------------------------------------------- /web/css/index.css: -------------------------------------------------------------------------------- 1 | /*首页*/ 2 | 3 | .index { 4 | position: absolute; 5 | left: 0px; 6 | right: 0px; 7 | top: 0px; 8 | } 9 | #header { 10 | position: relative; 11 | background-image: url(../images/index-header.jpg); 12 | background-color: #000; 13 | background-size: cover; 14 | background-position: center center; 15 | background-attachment: fixed; 16 | color: #fff; 17 | text-align: center; 18 | padding: 4em 0; 19 | cursor: default; 20 | } 21 | #header .inner { 22 | margin: 0; 23 | padding: 4em 0 0; 24 | position: relative; 25 | z-index: 1; 26 | padding-left: 15px; 27 | padding-right: 15px; 28 | } 29 | #header header { 30 | display: inline-block; 31 | margin-bottom: 35px; 32 | } 33 | #header h1 a{ 34 | color: #fff; 35 | } 36 | #header hr { 37 | top: 1.5em; 38 | margin-bottom: 3em; 39 | border-bottom-color: rgba(192, 192, 192, 0.35); 40 | box-shadow: inset 0 1px 0 0 rgba(192, 192, 192, 0.35); 41 | } 42 | #header hr:before, #header hr:after { 43 | background: none repeat scroll 0 0 rgba(192, 192, 192, 0.35); 44 | } 45 | #header hr { 46 | position: relative; 47 | display: block; 48 | border: 0; 49 | top: 1.5em; 50 | height: 6px; 51 | margin-bottom: 3em; 52 | box-shadow: inset 0 1px 0 0 rgba(192, 192, 192, 0.35); 53 | border-top: solid 1px rgba(128, 128, 128, 0.2); 54 | border-bottom: solid 1px rgba(128, 128, 128, 0.2); 55 | border-bottom-color: rgba(192, 192, 192, 0.35); 56 | } 57 | #header hr:before, 58 | #header hr:after { 59 | content: ''; 60 | position: absolute; 61 | top: -8px; 62 | display: block; 63 | width: 1px; 64 | height: 21px; 65 | background: rgba(128, 128, 128, 0.2); 66 | } 67 | #header hr:before { 68 | left: -1px; 69 | } 70 | #header hr:after { 71 | right: -1px; 72 | } 73 | .register { 74 | margin-right: 30px; 75 | } 76 | .login { 77 | font-size: 18px; 78 | color: #fff; 79 | } -------------------------------------------------------------------------------- /web/css/ui.css: -------------------------------------------------------------------------------- 1 | .avatar { 2 | width: 40px; 3 | height: 40px; 4 | } 5 | .avatar-xl { 6 | width: 128px; 7 | height: 128px; 8 | } 9 | .avatar-sm { 10 | width: 32px; 11 | height: 32px; 12 | } 13 | .avatar-xs { 14 | width: 24px; 15 | height: 24px; 16 | } 17 | .back-top, .back-top:focus { 18 | position: fixed; 19 | bottom: 15px; 20 | right: 50%; 21 | margin-right: -395px; 22 | width: 50px; 23 | height: 50px; 24 | display: block; 25 | border-radius: 0 7px 7px 0; 26 | background-color: #EEE; 27 | 28 | 29 | line-height: 54px; 30 | text-align: center; 31 | font-size: 30px; 32 | color: #fff; 33 | 34 | display: none; 35 | } 36 | .back-top:hover { 37 | background-color: #CCC; 38 | color: #fff; 39 | } 40 | .back-top .glyphicon-arrow-up { 41 | margin-left: -3px; 42 | } 43 | .footer { 44 | border-top: 1px solid #EEE; 45 | } -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yii2-chinesization/yii-qa/131211ae7643b3787cc1870488c03bc9def8e6de/web/favicon.ico -------------------------------------------------------------------------------- /web/images/anonymous.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yii2-chinesization/yii-qa/131211ae7643b3787cc1870488c03bc9def8e6de/web/images/anonymous.jpg -------------------------------------------------------------------------------- /web/index-test.php: -------------------------------------------------------------------------------- 1 | run(); 17 | -------------------------------------------------------------------------------- /web/index.php: -------------------------------------------------------------------------------- 1 | run(); 13 | -------------------------------------------------------------------------------- /web/js/global.js: -------------------------------------------------------------------------------- 1 | /* Global */ 2 | (function ($, underfined) { 3 | //站点设置 4 | var settings = { 5 | modalTemplate: '' + 6 | '' 11 | }; 12 | 13 | 14 | $(function() { 15 | // Scroll body to 0px on click 16 | var $topButton = $('[data-toggle^="backTop"]'); 17 | $topButton.click(function (e) { 18 | $('body, html').animate({ 19 | scrollTop: 0 20 | }, 800); 21 | e.preventDefault(); 22 | }); 23 | $(window).scroll(function () { 24 | var scrollTop = $(this).scrollTop(); 25 | $topButton[scrollTop > 100 ? 'fadeIn' : 'fadeOut'](300); 26 | }); 27 | 28 | // 自动为modal注册 remote 加载载体 29 | $('[data-toggle^="modal"]').each(function() { 30 | var $this = $(this), 31 | href = $this.attr('href'), 32 | target = $this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, '')), 33 | symbol = target.substr(0, 1); 34 | if (!href || !target || (symbol !== '.' && symbol !== '#')) return ; 35 | $(settings.modalTemplate) 36 | .attr((symbol == '#' ? 'id' : 'class'), target.substr(1, target.length)) 37 | .appendTo('body'); 38 | }); 39 | 40 | //自动注册 tooltip 事件 41 | $('[data-toggle=tooltip]').tooltip() 42 | }); 43 | })(jQuery); -------------------------------------------------------------------------------- /web/js/index.js: -------------------------------------------------------------------------------- 1 | jQuery(function($){ 2 | /* ================= 首页设置 ================= */ 3 | var indexSettings = { 4 | // 首页顶部大屏设置 5 | header: { 6 | domName: '#header', 7 | fullScreen: true, 8 | fadeIn: true, 9 | fadeDelay: 500 10 | } 11 | }; 12 | 13 | /* ================= 逻辑处理 ================= */ 14 | var $window = $(window), 15 | $body = $('body'), 16 | $header = $(indexSettings.header.domName), 17 | _IEVersion = (navigator.userAgent.match(/MSIE ([0-9]+)\./) ? parseInt(RegExp.$1) : 99), 18 | _isTouch = !!('ontouchstart' in window), 19 | _isMobile = !!(navigator.userAgent.match(/(iPod|iPhone|iPad|Android|IEMobile)/)); 20 | 21 | /** 22 | * 首页顶部大屏逻辑 23 | */ 24 | if (_isMobile) { 25 | $header.css('background-attachment', 'scroll'); 26 | indexSettings.header.fullScreen = false; 27 | } 28 | if (indexSettings.header.fullScreen) { 29 | $window.bind('resize.helios', function() { 30 | window.setTimeout(function() { 31 | var s = $header.children('.inner'); 32 | var sh = s.outerHeight(), hh = $window.height(), h = Math.ceil((hh - sh) / 2) + 1; 33 | 34 | $header 35 | .css('padding-top', h) 36 | .css('padding-bottom', h); 37 | }, 0); 38 | }).trigger('resize'); 39 | } 40 | if (indexSettings.header.fadeIn) { 41 | $('
').appendTo($header); 42 | $window.load(function() { 43 | var imageURL = $header.css('background-image').replace(/"/g,"").replace(/url\(|\)$/ig, ""); 44 | // $.n33_preloadImage(imageURL, function() { 45 | // if (_IEVersion < 10) { 46 | // $header.children('.overlay').fadeOut(2000); 47 | // } else { 48 | // window.setTimeout(function() { 49 | // $header.addClass('ready'); 50 | // }, helios_settings.header.fadeDelay); 51 | // } 52 | // }); 53 | }); 54 | } 55 | }); -------------------------------------------------------------------------------- /web/nginx.htaccess: -------------------------------------------------------------------------------- 1 | #禁止访问.htaccess文件 2 | location ~ .*\.(htaccess)?$ 3 | { 4 | deny all; 5 | } 6 | 7 | #0.8.6以上版本nginx rewrite规则 8 | location / 9 | { 10 | try_files $uri $uri/ /index.php?$args; 11 | } -------------------------------------------------------------------------------- /web/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: -------------------------------------------------------------------------------- /widgets/AjaxModal.php: -------------------------------------------------------------------------------- 1 | getRequest()->getIsAjax()) return; 19 | ob_start(); 20 | ob_implicit_flush(false); 21 | } 22 | 23 | public function run() 24 | { 25 | if (!Yii::$app->getRequest()->getIsAjax()) return; 26 | return $this->render('ajaxModal', [ 27 | 'header' => $this->header, 28 | 'content' => ob_get_clean(), 29 | 'footer' => $this->footer 30 | ]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /widgets/Alert.php: -------------------------------------------------------------------------------- 1 | '', 11 | 'error' => '', 12 | 'info' => '', 13 | 'waring' => '', 14 | ]; 15 | 16 | /** 17 | * @var array the alert types configuration for the flash messages. 18 | * This array is setup as $key => $value, where: 19 | * - $key is the name of the session flash variable 20 | * - $value is the bootstrap alert type (i.e. danger, success, info, warning) 21 | */ 22 | public $alertTypes = [ 23 | 'error' => 'alert-danger', 24 | 'danger' => 'alert-danger', 25 | 'success' => 'alert-success', 26 | 'info' => 'alert-info', 27 | 'warning' => 'alert-warning' 28 | ]; 29 | 30 | /** 31 | * @var array the options for rendering the close button tag. 32 | */ 33 | public $closeButton = []; 34 | 35 | public function init() 36 | { 37 | parent::init(); 38 | 39 | $session = Yii::$app->getSession(); 40 | $flashes = $session->getAllFlashes(); 41 | $appendCss = isset($this->options['class']) ? ' ' . $this->options['class'] : ''; 42 | 43 | foreach ($flashes as $type => $data) { 44 | if (isset($this->alertTypes[$type])) { 45 | $data = (array)$data; 46 | foreach ($data as $message) { 47 | /* initialize css class for each alert box */ 48 | $this->options['class'] = $this->alertTypes[$type] . $appendCss; 49 | 50 | /* assign unique id to each alert box */ 51 | $this->options['id'] = $this->getId() . '-' . $type; 52 | 53 | echo \yii\bootstrap\Alert::widget([ 54 | 'body' => $this->renderIcon($type) . "\n" . $message, 55 | 'closeButton' => $this->closeButton, 56 | 'options' => $this->options, 57 | ]); 58 | } 59 | 60 | $session->removeFlash($type); 61 | } 62 | } 63 | } 64 | 65 | public function renderIcon($type) 66 | { 67 | return isset($this->icones[$type]) ? $this->icones[$type] : ''; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /widgets/TopMenu.php: -------------------------------------------------------------------------------- 1 | render('topMenu', [ 17 | 'items' => $this->items() 18 | ]); 19 | } 20 | 21 | /** 22 | * 菜单选项 23 | * @return array 24 | */ 25 | public function items() 26 | { 27 | $user = Yii::$app->getUser(); 28 | $identity = $user->getIdentity(); 29 | if ($user->getIsGuest()) { 30 | $items = [ 31 | [ 32 | 'label' => '登录', 33 | 'url' => $user->loginUrl, 34 | 'linkOptions' => [ 35 | 'id' => 'login', 36 | 'data-toggle' => "modal", 37 | 'data-target' => '#loginModal' 38 | ] 39 | ], 40 | [ 41 | 'label' => '注册', 42 | 'url' => $user->registerUrl, 43 | 'linkOptions' => [ 44 | 'id' => 'register', 45 | 'data-toggle' => "modal", 46 | 'data-target' => '#registerModal' 47 | ] 48 | ], 49 | ]; 50 | } else { 51 | $items = [ 52 | [ 53 | 'label' => Html::img($identity->getAvatarUrl([ 54 | 'width' => 32, 55 | 'height' => 32 56 | ]), [ 57 | 'class' => 'avatar-xs', 58 | ]) . ' ' . $identity->username, 59 | 'items' => [ 60 | [ 61 | 'label' => ' 个人中心', 62 | 'url' => ['/user/home/index', 'id' => $user->id] 63 | ], 64 | [ 65 | 'label' => ' 后台管理', 66 | 'url' => ['/admin'], 67 | 'visible' => $user->can('visitAdmin') 68 | ], 69 | '
  • ', 70 | [ 71 | 'label' => ' 退出登录', 72 | 'url' => $user->logoutUrl 73 | ] 74 | ] 75 | ] 76 | ]; 77 | } 78 | 79 | return $items; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /widgets/views/ajaxModal.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | 14 | 17 | 18 | 21 | -------------------------------------------------------------------------------- /widgets/views/topMenu.php: -------------------------------------------------------------------------------- 1 | ['class' => 'navbar-nav navbar-right'], 6 | 'encodeLabels' => false, 7 | 'items' => $items 8 | ]); -------------------------------------------------------------------------------- /yii: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 24 | exit($exitCode); 25 | -------------------------------------------------------------------------------- /yii.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Yii command line bootstrap script for Windows. 5 | rem 6 | rem @author Qiang Xue 7 | rem @link http://www.yiiframework.com/ 8 | rem @copyright Copyright © 2012 Yii Software LLC 9 | rem @license http://www.yiiframework.com/license/ 10 | rem ------------------------------------------------------------- 11 | 12 | @setlocal 13 | 14 | set YII_PATH=%~dp0 15 | 16 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 17 | 18 | "%PHP_COMMAND%" "%YII_PATH%yii" %* 19 | 20 | @endlocal 21 | --------------------------------------------------------------------------------