├── README.md ├── composer.json └── src ├── actions ├── BaseAction.php └── UploadAction.php ├── assets ├── BaseAsset.php ├── FullScreenAsset.php └── assets │ └── plugin-full-screen1.0 │ ├── wangEditor-fullscreen-plugin.css │ └── wangEditor-fullscreen-plugin.js └── widgets └── WangEditorWidget.php /README.md: -------------------------------------------------------------------------------- 1 | Yii2 wangEditor widget 2 | ====================== 3 | 4 | Yii2 wangEditor widget 5 | 6 | v2.x 与 [v1.x](https://github.com/krissss/yii2-wang-editor/tree/v1.2) 版本之间存在差异,升级请注意 7 | 8 | [wangEditor 官网](http://www.wangeditor.com/) 9 | 10 | 安装 11 | ------------ 12 | 13 | ``` 14 | composer require kriss/yii2-wang-editor 15 | ``` 16 | 17 | 使用 18 | ----- 19 | 20 | ### widget 21 | 22 | ```php 23 | 'inputName', 28 | //'canFullScreen' => true, // 增加全屏的按钮 29 | //'customConfig' => [], // 扩展配置 30 | ]); 31 | // or 32 | echo $form->field($model, 'content')->widget(WangEditorWidget::class, [ 33 | //'canFullScreen' => true, 34 | ]); 35 | ``` 36 | 37 | ### action 38 | 39 | ```php 40 | [ 53 | 'class' => UploadAction::class, 54 | 'savePath' => '@webroot/uploads', 55 | 'displayPath' => '@web/uploads', 56 | ], 57 | ]; 58 | } 59 | } 60 | ``` 61 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kriss/yii2-wang-editor", 3 | "description": "Yii2 wangEditor widget", 4 | "type": "yii2-extension", 5 | "keywords": ["yii2","extension","wangEditor","editor"], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "kriss", 10 | "email": "462679766@qq.com" 11 | } 12 | ], 13 | "require": { 14 | "yiisoft/yii2": "~2.0.0", 15 | "npm-asset/wangeditor": "^3.0" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "kriss\\wangEditor\\": "src" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/actions/BaseAction.php: -------------------------------------------------------------------------------- 1 | response->format = Response::FORMAT_JSON; 36 | Yii::$app->request->enableCsrfValidation = false; 37 | if ($this->normalizePath === 'auto') { 38 | $this->normalizePath = strpos($this->savePath, '..') !== false; 39 | } 40 | } 41 | 42 | protected function getSaveFilename($filename) 43 | { 44 | return $this->getFullFilename($filename, $this->savePath); 45 | } 46 | 47 | protected function getDisplayFilename($filename) 48 | { 49 | return $this->getFullFilename($filename, $this->displayPath); 50 | } 51 | 52 | protected function getFullFilename($filename, $path) 53 | { 54 | $filename = Yii::getAlias(rtrim($path, '/') . '/' . $filename); 55 | if ($this->normalizePath) { 56 | return FileHelper::normalizePath($filename); 57 | } 58 | return $filename; 59 | } 60 | 61 | protected function solveDisplay2SaveFilename($displayFilename) 62 | { 63 | $displayPath = Yii::getAlias(rtrim($this->displayPath, '/')); 64 | if ($this->normalizePath) { 65 | $displayPath = FileHelper::normalizePath($displayPath); 66 | $displayFilename = FileHelper::normalizePath($displayFilename); 67 | } 68 | $filename = str_replace($displayPath, '', $displayFilename); 69 | $filename = Yii::getAlias(rtrim($this->savePath) . '/' . ltrim($filename, '/')); 70 | if ($this->normalizePath) { 71 | return FileHelper::normalizePath($filename); 72 | } 73 | return $filename; 74 | } 75 | 76 | public function returnError($msg, $code = 422) 77 | { 78 | return [ 79 | 'errno' => $code, 80 | 'code' => $code, 81 | 'msg' => $msg, 82 | ]; 83 | } 84 | 85 | public function returnSuccess($data = [], $msg = 'ok', $code = 200) 86 | { 87 | return [ 88 | 'errno' => 0, 89 | 'code' => $code, 90 | 'msg' => $msg, 91 | 'data' => $data, 92 | ]; 93 | } 94 | 95 | protected function defaultMessage() 96 | { 97 | return [ 98 | UPLOAD_ERR_OK => '上传成功', 99 | UPLOAD_ERR_INI_SIZE => '超出 php.ini 中定义的 upload_max_filesize 大小', 100 | UPLOAD_ERR_FORM_SIZE => '上传文件超过 MAX_FILE_SIZE 指令中指定的HTML表单', 101 | UPLOAD_ERR_PARTIAL => '上传文件只有部分上传', 102 | UPLOAD_ERR_NO_FILE => '没有文件被上传', 103 | UPLOAD_ERR_NO_TMP_DIR => '缺少一个临时文件夹', 104 | UPLOAD_ERR_CANT_WRITE => '没有写文件到磁盘', 105 | UPLOAD_ERR_EXTENSION => '一个PHP扩展停止了文件上传', 106 | ]; 107 | } 108 | 109 | protected function resolveErrorMessage($value) 110 | { 111 | $messages = array_merge($this->defaultMessage(), $this->messageMap); 112 | return isset($messages[$value]) ? $messages[$value] : $value; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/actions/UploadAction.php: -------------------------------------------------------------------------------- 1 | ['png', 'jpeg', 'jpg', 'gif', 'webp', 'bmp'], 'mimeTypes' => 'image/*', 'maxSize' => 5 * 1024 * 1024] 25 | ]; 26 | /** 27 | * 文件名生成的方式,默认用 md5 28 | * @var callable 29 | */ 30 | public $fileSaveNameCallback; 31 | /** 32 | * 文件保存的方法,默认用 UploadedFile::saveAs() 33 | * @var callable 34 | */ 35 | public $saveFileCallback; 36 | /** 37 | * 是否创建文件夹 38 | * 对于将 savePath 设置为 '@webroot/uploads/' . date('Y-m-d') 时非常有用 39 | * @var bool 40 | */ 41 | public $createDirection = true; 42 | 43 | public function run() 44 | { 45 | $uploadedFiles = UploadedFile::getInstancesByName($this->fileParam); 46 | $resultData = []; 47 | foreach ($uploadedFiles as $uploadedFile) { 48 | if ($uploadedFile->error == UPLOAD_ERR_OK) { 49 | $validationModel = DynamicModel::validateData(['file' => $uploadedFile], $this->validationRules); 50 | if (!$validationModel->hasErrors()) { 51 | try { 52 | $isSuccess = $this->saveFile($uploadedFile); 53 | if ($isSuccess) { 54 | $resultData[] = $this->getFileName($uploadedFile, $this->displayPath); 55 | continue; 56 | } else { 57 | return $this->returnError($this->resolveErrorMessage(static::MSG_UPLOAD_SAVE_ERROR)); 58 | } 59 | } catch (\Exception $e) { 60 | return $this->returnError($e->getMessage()); 61 | } 62 | } 63 | return $this->returnError($validationModel->getFirstError('file')); 64 | } 65 | return $this->returnError($this->resolveErrorMessage($uploadedFile->error)); 66 | } 67 | return $this->returnSuccess($resultData); 68 | } 69 | 70 | /** 71 | * @param $uploadedFile UploadedFile 72 | * @return bool 73 | */ 74 | protected function saveFile($uploadedFile) 75 | { 76 | $filename = $this->getFileName($uploadedFile, $this->savePath); 77 | if ($this->saveFileCallback && is_callable($this->saveFileCallback)) { 78 | return call_user_func($this->saveFileCallback, $filename, $uploadedFile, $this); 79 | } 80 | if ($this->createDirection) { 81 | FileHelper::createDirectory(dirname($filename)); 82 | } 83 | return $uploadedFile->saveAs($filename); 84 | } 85 | 86 | protected function defaultMessage() 87 | { 88 | return array_merge(parent::defaultMessage(), [ 89 | static::MSG_UPLOAD_SAVE_ERROR => '上传的文件保存失败', 90 | ]); 91 | } 92 | 93 | /** 94 | * @var false|string 95 | */ 96 | private $filenames = []; 97 | 98 | /** 99 | * @param $uploadedFile UploadedFile 100 | * @param $basePath string 101 | * @return string 102 | * @throws \Exception 103 | */ 104 | private function getFileName($uploadedFile, $basePath) 105 | { 106 | if (!isset($this->filenames[$uploadedFile->tempName])) { 107 | if ($this->fileSaveNameCallback && is_callable($this->fileSaveNameCallback)) { 108 | $filename = call_user_func($this->fileSaveNameCallback, $uploadedFile, $this); 109 | } else { 110 | $filename = md5(microtime() . random_int(10000, 99999)); 111 | } 112 | if (strpos($filename, '.') === false) { 113 | $filename .= '.' . $uploadedFile->getExtension(); 114 | } 115 | $this->filenames[$uploadedFile->tempName] = $filename; 116 | } 117 | $filename = $this->getFullFilename($this->filenames[$uploadedFile->tempName], $basePath); 118 | return $filename; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/assets/BaseAsset.php: -------------------------------------------------------------------------------- 1 | 全屏'); 8 | }, 9 | toggleFullscreen: function(editorSelector){ 10 | $(editorSelector).toggleClass('fullscreen-editor'); 11 | if($(editorSelector + ' ._wangEditor_btn_fullscreen').text() == '全屏'){ 12 | $(editorSelector + ' ._wangEditor_btn_fullscreen').text('退出全屏'); 13 | }else{ 14 | $(editorSelector + ' ._wangEditor_btn_fullscreen').text('全屏'); 15 | } 16 | } 17 | }; -------------------------------------------------------------------------------- /src/widgets/WangEditorWidget.php: -------------------------------------------------------------------------------- 1 | editorId = 'editor-' . $this->id; 46 | $this->editorObj = $this->id . 'Editor'; 47 | if ($this->uploadImageServer !== false) { 48 | $this->uploadImageServer = Url::to($this->uploadImageServer); 49 | } 50 | } 51 | 52 | public function run() 53 | { 54 | $html = []; 55 | if ($this->hasModel()) { 56 | $html[] = Html::activeHiddenInput($this->model, $this->attribute, $this->options); 57 | $content = Html::getAttributeValue($this->model, $this->attribute); 58 | } else { 59 | $html[] = Html::hiddenInput($this->name, $this->value, $this->options); 60 | $content = $this->value; 61 | } 62 | $html[] = $this->renderHtml($content); 63 | 64 | $this->registerAssets(); 65 | 66 | return implode("\n", $html); 67 | } 68 | 69 | protected function registerAssets() 70 | { 71 | $view = $this->getView(); 72 | BaseAsset::register($view); 73 | if ($this->canFullScreen) { 74 | FullScreenAsset::register($view); 75 | } 76 | 77 | $js[] = "var {$this->editorObj} = new window.wangEditor('#{$this->editorId}');"; 78 | $customConfig = array_merge($this->getDefaultCustomConfig(), $this->customConfig); 79 | if ($customConfig) { 80 | $customConfig = Json::htmlEncode($customConfig); 81 | $js[] = "{$this->editorObj}.customConfig = {$customConfig}"; 82 | } 83 | $js[] = "{$this->editorObj}.create();"; 84 | if ($this->canFullScreen) { 85 | $js[] = "window.wangEditor.fullscreen.init('#{$this->editorId}');"; 86 | } 87 | $view->registerJs(implode("\n", $js)); 88 | } 89 | 90 | protected function getDefaultCustomConfig() 91 | { 92 | $config = [ 93 | 'onchange' => new JsExpression("function(html){\$('#{$this->options['id']}').val(html);}"), 94 | 'menus' => [ 95 | 'head', // 标题 96 | 'bold', // 粗体 97 | 'fontSize', // 字号 98 | 'fontName', // 字体 99 | 'italic', // 斜体 100 | 'underline', // 下划线 101 | 'strikeThrough', // 删除线 102 | 'foreColor', // 文字颜色 103 | 'backColor', // 背景颜色 104 | 'link', // 插入链接 105 | 'list', // 列表 106 | 'justify', // 对齐方式 107 | 'quote', // 引用 108 | //'emoticon', // 表情 109 | 'image', // 插入图片 110 | 'table', // 表格 111 | 'video', // 插入视频 112 | 'code', // 插入代码 113 | 'undo', // 撤销 114 | 'redo' // 重复 115 | ], 116 | //'uploadImgMaxSize' => 5242880, // 5M 117 | //'uploadImgMaxLength' => 10, 118 | ]; 119 | if ($this->uploadImageServer) { 120 | $config['uploadImgServer'] = $this->uploadImageServer; 121 | $config['uploadFileName'] = 'filename[]'; 122 | $config['uploadImgHooks'] = [ 123 | 'fail' => new JsExpression('function (xhr, editor, result) {alert(result.msg);}'), 124 | ]; 125 | } 126 | return $config; 127 | } 128 | 129 | protected function renderHtml($content) 130 | { 131 | return Html::tag('div', $content, ['id' => $this->editorId]); 132 | } 133 | } 134 | --------------------------------------------------------------------------------