首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

Yii 框架整合 KindEditor 富文本編輯器:自定義 Widget、圖片上傳/瀏覽/管理完整實現

     編輯:PHP 相關知識

Yii 框架整合 KindEditor 富文本編輯器:自定義 Widget、圖片上傳/瀏覽/管理完整實現

KindEditor 以輕量、國產化支持好著稱。本文展示在 Yii 1.x 中封裝 KEditor Widget,實現編輯器渲染、圖片/文件上傳、服務器文件瀏覽管理的完整一體化方案。

目錄結構規劃

protected/extensions/KEditor/
├── KEditor.php           # 主 Widget 類
├── KEditorManage.php     # 文件瀏覽管理 Action
├── KEditorUpload.php     # 文件上傳 Action
└── keSource/             # KindEditor 源碼目錄
    ├── kindeditor.js
    ├── kindeditor-min.js
    ├── themes/
    ├── plugins/
    └── lang/

1. 主 Widget:KEditor.php

<?php
class KEditor extends CWidget {
    public $model;              // 表單模型
    public $name;               // 字段名 (必填)
    public $id;                 // DOM ID (可選)
    public $textareaOptions = []; // textarea 屬性
    public $properties = [];    // KindEditor 配置屬性
    private $_baseUrl;          // 資源發布 URL

    public static function getUploadPath() {
        if (isset(Yii::app()->params['uploadPath'])) {
            return Yii::getPathOfAlias('webroot') . str_replace('/', DIRECTORY_SEPARATOR, Yii::app()->params['uploadPath']);
        }
        $dir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'keSource';
        return Yii::app()->getAssetManager()->getPublishedPath($dir) . DIRECTORY_SEPARATOR . 'upload';
    }

    public static function getUploadUrl() {
        if (isset(Yii::app()->params['uploadPath'])) {
            return Yii::app()->baseUrl . Yii::app()->params['uploadPath'];
        }
        $dir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'keSource';
        return Yii::app()->getAssetManager()->publish($dir) . '/upload';
    }

    public function init() {
        if ($this->name === null) throw new CException('name 屬性不能為空');
        
        $dir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'keSource';
        $this->_baseUrl = Yii::app()->getAssetManager()->publish($dir);
        
        $cs = Yii::app()->getClientScript();
        $cs->registerCssFile($this->_baseUrl . '/themes/default/default.css');
        $cs->registerScriptFile(YII_DEBUG ? $this->_baseUrl . '/kindeditor.js' : $this->_baseUrl . '/kindeditor-min.js');
    }

    public function run() {
        $cs = Yii::app()->getClientScript();
        
        // 渲染 textarea
        $taOptions = $this->getTextareaOptions();
        $taOptions['name'] = CHtml::resolveName($this->model, $this->name);
        $this->id = $taOptions['id'] = CHtml::getIdByName($taOptions['name']);
        echo CHtml::activeTextArea($this->model, $this->name, $taOptions);

        // 初始化 KindEditor
        $props = CJavaScript::encode($this->getKeProperties());
        $js = <<id} = K.create('#{$this->id}', {$props});
});
EOF;
        $cs->registerScript('KE_' . $this->name, $js, CClientScript::POS_HEAD);
    }

    protected function getTextareaOptions() {
        $allow = ['rows', 'cols', 'style'];
        $params = [];
        foreach ($allow as $key) {
            if (isset($this->textareaOptions[$key])) $params[$key] = $this->textareaOptions[$key];
        }
        $params['name'] = $params['id'] = $this->name;
        return $params;
    }

    protected function getKeProperties() {
        $allowKeys = [
            'width', 'height', 'minWidth', 'minHeight', 'items', 'noDisableItems',
            'filterMode', 'htmlTags', 'wellFormatMode', 'resizeType', 'themeType',
            'langType', 'designMode', 'fullscreenMode', 'basePath', 'themesPath',
            'pluginsPath', 'langPath', 'minChangeSize', 'urlType', 'newlineTag',
            'pasteType', 'dialogAlignType', 'shadowMode', 'useContextmenu',
            'syncType', 'indentChar', 'cssPath', 'cssData', 'bodyClass',
            'colorTable', 'afterCreate', 'afterChange', 'afterTab', 'afterFocus',
            'afterBlur', 'afterUpload', 'uploadJson', 'fileManagerJson',
            'allowPreviewEmoticons', 'allowImageUpload', 'allowFlashUpload',
            'allowMediaUpload', 'allowFileUpload', 'allowFileManager',
            'fontSizeTable', 'imageTabIndex', 'formatUploadUrl', 'fullscreenShortcut',
            'extraFileUploadParams'
        ];
        $params = [];
        foreach ($allowKeys as $key) {
            if (isset($this->properties[$key])) $params[$key] = $this->properties[$key];
        }
        return $params;
    }
}

2. 文件瀏覽管理:KEditorManage.php

<?php
class KEditorManage extends CAction {
    public function run() {
        Yii::import('ext.KEditor.KEditor');
        $rootPath = KEditor::getUploadPath() . '/';
        $rootUrl  = KEditor::getUploadUrl() . '/';

        $extArr = ['gif', 'jpg', 'jpeg', 'png', 'bmp'];
        $dirName = empty($_GET['dir']) ? '' : trim($_GET['dir']);
        if (!in_array($dirName, ['', 'image', 'flash', 'media', 'file'])) {
            echo 'Invalid Directory name.'; exit;
        }

        if ($dirName) {
            $rootPath .= $dirName . '/';
            $rootUrl  .= $dirName . '/';
            if (!file_exists($rootPath)) mkdir($rootPath);
        }

        // 路徑解析
        if (empty($_GET['path'])) {
            $currentPath = realpath($rootPath) . '/';
            $currentUrl = $rootUrl;
            $currentDirPath = '';
            $moveupDirPath = '';
        } else {
            $currentPath = realpath($rootPath) . '/' . $_GET['path'];
            $currentUrl = $rootUrl . $_GET['path'];
            $currentDirPath = $_GET['path'];
            $moveupDirPath = preg_replace('/(.*\/)[^\/]+\/$/', '$1', $currentDirPath);
        }

        // 安全檢查
        if (strpos($currentPath, '..') !== false || !preg_match('/\/$/', $currentPath) || !is_dir($currentPath)) {
            echo 'Access denied.'; exit;
        }

        // 掃描文件
        $fileList = [];
        $handle = new DirectoryIterator($currentPath);
        foreach ($handle as $file) {
            if ($file->isDot()) continue;
            $item = [
                'filename' => $file->getFilename(),
                'datetime' => date('Y-m-d H:i:s', $file->getMTime()),
            ];
            if ($file->isDir()) {
                $item['is_dir'] = true;
                $item['has_file'] = count(scandir($file->getPathname())) > 2;
                $item['filesize'] = 0;
                $item['is_photo'] = false;
                $item['filetype'] = '';
            } else {
                $item['is_dir'] = false;
                $item['has_file'] = false;
                $item['filesize'] = $file->getSize();
                $ext = $file->getExtension();
                $item['is_photo'] = in_array($ext, $extArr);
                $item['filetype'] = $ext;
            }
            $fileList[] = $item;
        }

        // 排序
        $order = empty($_GET['order']) ? 'name' : strtolower($_GET['order']);
        usort($fileList, function($a, $b) use ($order) {
            if ($a['is_dir'] && !$b['is_dir']) return -1;
            if (!$a['is_dir'] && $b['is_dir']) return 1;
            if ($order === 'size') return $a['filesize'] - $b['filesize'];
            if ($order === 'type') return strcmp($a['filetype'], $b['filetype']);
            return strcmp($a['filename'], $b['filename']);
        });

        // 輸出 JSON
        header('Content-Type: application/json; charset=UTF-8');
        echo CJSON::encode([
            'moveup_dir_path' => $moveupDirPath,
            'current_dir_path' => $currentDirPath,
            'current_url' => $currentUrl,
            'total_count' => count($fileList),
            'file_list' => $fileList,
        ]);
        exit;
    }
}

3. 文件上傳處理:KEditorUpload.php

<?php
class KEditorUpload extends CAction {
    public function run() {
        $dir = isset($_GET['dir']) ? trim($_GET['dir']) : 'file';
        $extArr = [
            'image' => ['gif', 'jpg', 'jpeg', 'png', 'bmp'],
            'flash' => ['swf', 'flv'],
            'media' => ['swf', 'flv', 'mp3', 'wav', 'wma', 'wmv', 'mid', 'avi', 'mpg', 'asf', 'rm', 'rmvb'],
            'file'  => ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'htm', 'html', 'txt', 'zip', 'rar', 'gz', 'bz2'],
        ];
        if (empty($extArr[$dir])) {
            echo CJSON::encode(['error' => 1, 'message' => '目錄名不正確']); exit;
        }

        $maxSize = 2 * 1024 * 1024; // 2MB
        $uploadImage = CUploadedFile::getInstanceByName('imgFile');
        Yii::import('ext.KEditor.KEditor');

        $uploadDir = KEditor::getUploadPath() . '/' . $dir;
        if (!file_exists($uploadDir)) mkdir($uploadDir);
        $dateDir = $uploadDir . '/' . date('Ymd');
        if (!file_exists($dateDir)) mkdir($dateDir);

        $uploadUrl = KEditor::getUploadUrl() . '/' . $dir . '/' . date('Ymd');

        if ($uploadImage instanceof CUploadedFile) {
            if ($uploadImage->size > $maxSize) {
                echo CJSON::encode(['error' => 1, 'message' => '文件過大']); exit;
            }
            $ext = $uploadImage->extensionName;
            if (!in_array($ext, $extArr[$dir])) {
                echo CJSON::encode(['error' => 1, 'message' => '不允許的擴展名']); exit;
            }

            $filename = date('YmdHis') . '_' . rand(10000, 99999) . '.' . $ext;
            $savePath = $dateDir . '/' . $filename;
            $fileUrl = $uploadUrl . '/' . $filename;

            $uploadImage->saveAs($savePath);
            echo CJSON::encode(['error' => 0, 'url' => $fileUrl]);
        } else {
            echo CJSON::encode(['error' => 1, 'message' => '上傳失敗']);
        }
    }
}

4. 配置與使用

main.php 參數配置

'params' => array(
    'adminEmail' => 'webmaster@example.com',
    'uploadPath' => '/upload', // 站點根目錄下 upload 文件夾
),

控制器註冊 Action

class DefaultController extends Controller {
    public function actions() {
        return array(
            'upload' => array('class' => 'application.extensions.KEditor.KEditorUpload'),
            'manageJson' => array('class' => 'application.extensions.KEditor.KEditorManage'),
        );
    }
}

視圖中使用 Widget

<?php $this->widget('ext.KEditor.KEditor', array(
    'model' => $model,
    'name' => 'content',
    'properties' => array(
        'uploadJson' => '/admin/default/upload',
        'fileManagerJson' => '/admin/default/manageJson',
        'newlineTag' => 'br',
        'allowFileManager' => true,
        'afterCreate' => "js:function() {
            K('#ChapterForm_all_len').val(this.count());
            K('#ChapterForm_word_len').val(this.count('text'));
        }",
        'afterChange' => "js:function() {
            K('#ChapterForm_all_len').val(this.count());
            K('#ChapterForm_word_len').val(this.count('text'));
        }",
    ),
    'textareaOptions' => array(
        'style' => 'width:98%;height:400px;',
    ),
)); ?>

JS 原生配置對照 (驗證一致性)

var editor1 = K.create('#editor_modelname_name', {
    uploadJson: "/admin/default/upload",
    fileManagerJson: "/admin/default/manageJson",
    newlineTag: "br",
    allowFileManager: true,
    afterCreate: function() {
        K('#ChapterForm_all_len').html(this.count());
        K('#ChapterForm_word_len').html(this.count('text'));
    },
    afterChange: function() {
        K('#ChapterForm_all_len').html(this.count());
        K('#ChapterForm_word_len').html(this.count('text'));
    }
});

關鍵優化與安全點

項目措施
上傳目錄執行權限Nginx: location /upload { php_flag engine off; } Apache: php_admin_flag engine off
文件類型驗證雙重校驗:擴展名白名單 + getimagesize() 驗證圖片真實性
文件名隨機化時間戳 + 隨機數,避免覆蓋與猜解
大小限制前端配置 + 後端 $maxSize 雙重限制
瀏覽目錄遍歷防護strpos($currentPath, '..') 檢查 + realpath() 規範化
資源發布AssetManager 發布 keSource 目錄,支持調試/壓縮切換

總結

通過 KEditor Widget 封裝渲染、KEditorUpload 處理上傳、KEditorManage 提供文件瀏覽 三位一體架構,實現了 KindEditor 在 Yii 中的深度整合。核心優勢在於:配置屬性透傳、JS 回調支持、上傳/瀏覽路徑統一管理、AssetManager 自動發布靜態資源。此模式同樣適用於 CKEditor、TinyMCE、UEditor 等編輯器整合。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved