diff --git a/module/admin/config.php b/module/admin/config.php
index 6739ff17f1..692c530beb 100755
--- a/module/admin/config.php
+++ b/module/admin/config.php
@@ -14,7 +14,7 @@ $config->admin->menuGroup['model'] = array('auditcl', 'stage', 'design', 'cm
$config->admin->menuGroup['feature'] = array('custom|set', 'custom|product', 'custom|execution', 'custom|required', 'custom|kanban', 'approvalflow', 'measurement', 'meetingroom', 'custom|browsestoryconcept', 'custom|kanban', 'sqlbuilder', 'report');
$config->admin->menuGroup['template'] = array('custom|set', 'baseline');
$config->admin->menuGroup['message'] = array('mail', 'webhook', 'sms', 'message');
-$config->admin->menuGroup['dev'] = array('dev', 'entry');
+$config->admin->menuGroup['dev'] = array('dev', 'entry', 'editor');
$config->admin->menuGroup['extension'] = array('extension');
$config->admin->menuGroup['convert'] = array('convert');
diff --git a/module/admin/lang/menu.php b/module/admin/lang/menu.php
index d7ff4fa7c8..4d4f4c81c1 100644
--- a/module/admin/lang/menu.php
+++ b/module/admin/lang/menu.php
@@ -165,7 +165,7 @@ $lang->admin->menuList->message['menuOrder']['25'] = 'setting';
$lang->admin->menuList->dev['subMenu']['api'] = array('link' => "{$lang->api->doc}|dev|api|module=index");
$lang->admin->menuList->dev['subMenu']['db'] = array('link' => "{$lang->database}|dev|db|table=" . trim(TABLE_EFFORT, '`'));
$lang->admin->menuList->dev['subMenu']['langItem'] = array('link' => "{$lang->langItem}|dev|langitem|");
-$lang->admin->menuList->dev['subMenu']['editor'] = array('link' => "{$lang->editor}|dev|editor|");
+$lang->admin->menuList->dev['subMenu']['editor'] = array('link' => "{$lang->editor->common}|dev|editor|", 'subModule' => 'editor');
$lang->admin->menuList->dev['subMenu']['entry'] = array('link' => "{$lang->admin->entry}|entry|browse|", 'subModule' => 'entry');
$lang->admin->menuList->dev['menuOrder']['5'] = 'api';
diff --git a/module/admin/model.php b/module/admin/model.php
index c23ea0103c..3d6d92951e 100755
--- a/module/admin/model.php
+++ b/module/admin/model.php
@@ -333,6 +333,13 @@ class adminModel extends model
/* Check sub menu priv. */
foreach($subMenuList as $subMenuKey => $subMenu)
{
+ if($menuKey == 'message' and $subMenuKey == 'mail')
+ {
+ $this->loadModel('mail');
+ if(!$this->config->mail->turnon and !$this->session->mailConfig) $subMenu['link'] = $this->lang->mail->common . '|mail|detect|';
+ }
+ if($menuKey == 'dev' and $subMenuKey == 'editor' and $this->config->global->editor) $subMenu['link'] = $this->lang->editor->common . '|editor|index|';
+
$link = array();
if(isset($menu['tabMenu'][$subMenuKey]))
{
@@ -362,12 +369,6 @@ class adminModel extends model
}
else
{
- if($menuKey == 'message' and $subMenuKey == 'mail')
- {
- $this->loadModel('mail');
- if(!$this->config->mail->turnon and !$this->session->mailConfig) $subMenu['link'] = $this->lang->mail->common . '|mail|detect|';
- }
-
$link = $this->getHasPrivLink($subMenu);
}
@@ -640,17 +641,18 @@ class adminModel extends model
*/
public function checkInternet()
{
+ $timeoutMS = 500;
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $this->config->admin->apiSite);
- curl_setopt($curl, CURLOPT_TIMEOUT_MS, 1000);
- curl_setopt($curl, CURLOPT_CONNECTTIMEOUT_MS, 1000);
+ curl_setopt($curl, CURLOPT_TIMEOUT_MS, $timeoutMS);
+ curl_setopt($curl, CURLOPT_CONNECTTIMEOUT_MS, $timeoutMS);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
$connected = curl_exec($curl);
curl_close($curl);
- return (bool)$connected;
+ return $connected ? true : false;
}
/**
diff --git a/module/common/lang/common.php b/module/common/lang/common.php
index 5ed798f09b..cfeeb76a0d 100644
--- a/module/common/lang/common.php
+++ b/module/common/lang/common.php
@@ -65,6 +65,7 @@ $lang->custom = new stdclass();
$lang->mail = new stdclass();
$lang->cron = new stdclass();
$lang->dev = new stdclass();
+$lang->editor = new stdclass();
$lang->entry = new stdclass();
$lang->webhook = new stdclass();
$lang->message = new stdclass();
diff --git a/module/common/lang/de.php b/module/common/lang/de.php
index eca0fd20b9..35d6242fad 100644
--- a/module/common/lang/de.php
+++ b/module/common/lang/de.php
@@ -194,6 +194,7 @@ $lang->extension->common = 'Extension';
$lang->company->common = 'Company';
$lang->dept->common = 'Dept';
$lang->upgrade->common = 'Update';
+$lang->editor->common = 'Editor';
$lang->program->list = 'Program List';
$lang->program->kanban = 'Program Kanban';
$lang->design->common = 'Design';
@@ -258,7 +259,6 @@ $lang->db = 'Database';
$lang->langItem = 'Lang Item';
$lang->api->doc = 'API Document';
$lang->database = 'Data Dictionary';
-$lang->editor = 'Editor';
$lang->timezone = 'Timezone';
$lang->security = 'Security';
$lang->calendar = 'Calendar';
diff --git a/module/common/lang/en.php b/module/common/lang/en.php
index 19be481dd7..73719622d0 100644
--- a/module/common/lang/en.php
+++ b/module/common/lang/en.php
@@ -194,6 +194,7 @@ $lang->extension->common = 'Extension';
$lang->company->common = 'Company';
$lang->dept->common = 'Dept';
$lang->upgrade->common = 'Update';
+$lang->editor->common = 'Editor';
$lang->program->list = 'Program List';
$lang->program->kanban = 'Program Kanban';
$lang->design->common = 'Design';
@@ -258,7 +259,6 @@ $lang->db = 'Database';
$lang->langItem = 'Lang Item';
$lang->api->doc = 'API Document';
$lang->database = 'Data Dictionary';
-$lang->editor = 'Editor';
$lang->timezone = 'Timezone';
$lang->security = 'Security';
$lang->calendar = 'Calendar';
diff --git a/module/common/lang/fr.php b/module/common/lang/fr.php
index 09e796c036..c25d7e0ddd 100644
--- a/module/common/lang/fr.php
+++ b/module/common/lang/fr.php
@@ -194,6 +194,7 @@ $lang->extension->common = 'Extension';
$lang->company->common = 'Company';
$lang->dept->common = 'Dept';
$lang->upgrade->common = 'Update';
+$lang->editor->common = 'Editor';
$lang->program->list = 'Program List';
$lang->program->kanban = 'Program Kanban';
$lang->design->common = 'Design';
@@ -258,7 +259,6 @@ $lang->db = 'Database';
$lang->langItem = 'Lang Item';
$lang->api->doc = 'API Document';
$lang->database = 'Data Dictionary';
-$lang->editor = 'Editor';
$lang->timezone = 'Timezone';
$lang->security = 'Security';
$lang->calendar = 'Calendar';
diff --git a/module/common/lang/menu.php b/module/common/lang/menu.php
index ef6839e1a6..b6b7f811de 100644
--- a/module/common/lang/menu.php
+++ b/module/common/lang/menu.php
@@ -693,6 +693,7 @@ $lang->navGroup->cron = 'admin';
$lang->navGroup->backup = 'admin';
$lang->navGroup->mail = 'admin';
$lang->navGroup->dev = 'admin';
+$lang->navGroup->editor = 'admin';
$lang->navGroup->entry = 'admin';
$lang->navGroup->extension = 'admin';
$lang->navGroup->action = 'admin';
diff --git a/module/common/lang/zh-cn.php b/module/common/lang/zh-cn.php
index ef7806bb23..d4e637f84b 100644
--- a/module/common/lang/zh-cn.php
+++ b/module/common/lang/zh-cn.php
@@ -194,6 +194,7 @@ $lang->extension->common = '插件';
$lang->company->common = '公司';
$lang->dept->common = '部门';
$lang->upgrade->common = '升级';
+$lang->editor->common = '编辑器';
$lang->program->list = '项目集列表';
$lang->program->kanban = '项目集看板';
$lang->design->common = '设计';
@@ -258,7 +259,6 @@ $lang->db = '数据库';
$lang->langItem = '语言项';
$lang->api->doc = '接口文档';
$lang->database = '数据字典';
-$lang->editor = '编辑器';
$lang->timezone = '时区';
$lang->security = '安全';
$lang->calendar = '日程';
diff --git a/module/dev/config.php b/module/dev/config.php
index fb212e0540..d45a2f2ddc 100644
--- a/module/dev/config.php
+++ b/module/dev/config.php
@@ -87,6 +87,7 @@ $config->dev->group['mail'] = 'admin';
$config->dev->group['backup'] = 'admin';
$config->dev->group['cron'] = 'admin';
$config->dev->group['dev'] = 'admin';
+$config->dev->group['editor'] = 'admin';
$config->dev->group['custom'] = 'admin';
$config->dev->group['score'] = 'admin';
$config->dev->group['config'] = 'admin';
diff --git a/module/dev/lang/de.php b/module/dev/lang/de.php
index fb484cc2d7..2bffeedf42 100644
--- a/module/dev/lang/de.php
+++ b/module/dev/lang/de.php
@@ -90,6 +90,7 @@ $lang->dev->tableList['backup'] = 'Backup';
$lang->dev->tableList['common'] = 'Common';
$lang->dev->tableList['convert'] = 'Konvertieren';
$lang->dev->tableList['dev'] = 'Entwickeln';
+$lang->dev->tableList['editor'] = 'Editor';
$lang->dev->tableList['git'] = 'GIT';
$lang->dev->tableList['index'] = 'Home';
$lang->dev->tableList['install'] = 'Installation';
diff --git a/module/dev/lang/en.php b/module/dev/lang/en.php
index d385377cdb..59eff282a3 100644
--- a/module/dev/lang/en.php
+++ b/module/dev/lang/en.php
@@ -90,6 +90,7 @@ $lang->dev->tableList['backup'] = 'Backup';
$lang->dev->tableList['common'] = 'Common';
$lang->dev->tableList['convert'] = 'Convert';
$lang->dev->tableList['dev'] = 'Develop';
+$lang->dev->tableList['editor'] = 'Editor';
$lang->dev->tableList['git'] = 'GIT';
$lang->dev->tableList['index'] = 'Home';
$lang->dev->tableList['install'] = 'Install';
diff --git a/module/dev/lang/fr.php b/module/dev/lang/fr.php
index d385377cdb..59eff282a3 100644
--- a/module/dev/lang/fr.php
+++ b/module/dev/lang/fr.php
@@ -90,6 +90,7 @@ $lang->dev->tableList['backup'] = 'Backup';
$lang->dev->tableList['common'] = 'Common';
$lang->dev->tableList['convert'] = 'Convert';
$lang->dev->tableList['dev'] = 'Develop';
+$lang->dev->tableList['editor'] = 'Editor';
$lang->dev->tableList['git'] = 'GIT';
$lang->dev->tableList['index'] = 'Home';
$lang->dev->tableList['install'] = 'Install';
diff --git a/module/dev/lang/zh-cn.php b/module/dev/lang/zh-cn.php
index 7929aff2f1..64292ec0cd 100644
--- a/module/dev/lang/zh-cn.php
+++ b/module/dev/lang/zh-cn.php
@@ -18,7 +18,7 @@ $lang->dev->paramRange = '取值范围:%s';
$lang->dev->paramDate = '日期格式:YY-mm-dd,如:2019-01-01';
$lang->dev->paramColor = '颜色格式:#RGB,如:#3da7f5';
$lang->dev->paramMailto = "填写帐号,多个账号用','分隔。";
-$lang->dev->noteEditor = "编辑器功能因为安全原因被禁用。使用该功能,请到官网下载安装 扩展编辑器 插件。";
+$lang->dev->noteEditor = "编辑器功能存在部分安全问题,如需使用该功能,可在此开启。";
$lang->dev->noteTranslate = "翻译功能因为安全原因被禁用。使用该功能,请到官网下载安装 翻译 插件。";
$lang->dev->confirmRestore = '是否要恢复默认?';
@@ -38,6 +38,9 @@ $lang->dev->fields['type'] = '类型';
$lang->dev->fields['length'] = '长度';
$lang->dev->fields['null'] = '是否可空';
+$lang->dev->switchList['1'] = '开启';
+$lang->dev->switchList['0'] = '关闭';
+
$lang->dev->tableList = array();
$lang->dev->tableList['action'] = '系统日志';
$lang->dev->tableList['bug'] = 'Bug';
@@ -90,6 +93,7 @@ $lang->dev->tableList['backup'] = '备份';
$lang->dev->tableList['common'] = '公有模块';
$lang->dev->tableList['convert'] = '导入';
$lang->dev->tableList['dev'] = '二次开发';
+$lang->dev->tableList['editor'] = '编辑器';
$lang->dev->tableList['git'] = 'GIT';
$lang->dev->tableList['index'] = '首页';
$lang->dev->tableList['install'] = '安装';
diff --git a/module/dev/view/editor.html.php b/module/dev/view/editor.html.php
index 31a05c3ff1..d0710305f0 100644
--- a/module/dev/view/editor.html.php
+++ b/module/dev/view/editor.html.php
@@ -12,6 +12,11 @@
?>
-
dev->noteEditor;?>
+
+ dev->noteEditor;
+ if(common::hasPriv('editor', 'switch')) echo html::a($this->createLink('editor', 'switch', 'status=1'), $lang->dev->switchList[1], 'hiddenwin', "class='btn btn-primary'");
+ ?>
+
diff --git a/module/editor/config.php b/module/editor/config.php
new file mode 100644
index 0000000000..2f67d55773
--- /dev/null
+++ b/module/editor/config.php
@@ -0,0 +1,16 @@
+editor->sort[] = 'model.php';
+$config->editor->sort[] = 'control.php';
+$config->editor->sort[] = 'view';
+$config->editor->sort[] = 'lang';
+$config->editor->sort[] = 'config.php';
+$config->editor->sort[] = 'js';
+$config->editor->sort[] = 'css';
+
+$config->editor->extSort[] = 'model';
+$config->editor->extSort[] = 'control';
+$config->editor->extSort[] = 'view';
+$config->editor->extSort[] = 'lang';
+$config->editor->extSort[] = 'js';
+$config->editor->extSort[] = 'css';
+$config->editor->extSort[] = 'config';
diff --git a/module/editor/control.php b/module/editor/control.php
new file mode 100644
index 0000000000..d67e302959
--- /dev/null
+++ b/module/editor/control.php
@@ -0,0 +1,182 @@
+
+ * @package editor
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+class editor extends control
+{
+ /**
+ * Show module files and edit them.
+ *
+ * @access public
+ * @return void
+ */
+ public function index($type = 'editor')
+ {
+ $this->app->loadLang('dev');
+ $this->view->title = $this->lang->editor->common;
+ $this->view->position[] = $this->lang->editor->common;
+ $this->view->modules = $this->loadModel('dev')->getModules();
+ $this->view->tab = $type;
+ $this->display();
+ }
+
+ /**
+ * Show files and methods of the module.
+ *
+ * @param string $moduleDir
+ * @access public
+ * @return void
+ */
+ public function extend($moduleDir = '')
+ {
+ $moduleFiles = $this->editor->getModuleFiles($moduleDir);
+ $this->view->module = $moduleDir;
+ $this->view->tree = $this->editor->printTree($moduleFiles);
+ $this->display();
+ }
+
+ /**
+ * Edit extend.
+ *
+ * @param string $filePath
+ * @param string $action
+ * @param string $isExtends
+ * @access public
+ * @return void
+ */
+ public function edit($filePath = '', $action = '', $isExtends = '')
+ {
+ $this->view->safeFilePath = $filePath;
+ $fileContent = '';
+ if($filePath)
+ {
+ $filePath = helper::safe64Decode($filePath);
+ if(strpos(strtolower($filePath), strtolower($this->app->getBasePath())) !== 0) die($this->lang->editor->editFileError);
+ if($action == 'extendOther' and file_exists($filePath))
+ {
+ $this->view->showContent = file_get_contents($filePath);
+ }
+ if($action == 'edit' or $action == 'override')
+ {
+ if(file_exists($filePath))
+ {
+ $fileContent = file_get_contents($filePath);
+ if($action == 'override')
+ {
+ $fileContent = str_replace("'../../", '$this->app->getModuleRoot() . \'', $fileContent);
+ $fileContent = str_replace(array('\'./', '"./'), array('\'../../view/', '"../../view'), $fileContent);
+ }
+ }
+ else
+ {
+ $filePath = '';
+ }
+ }
+ elseif($action == 'extendModel')
+ {
+ $fileContent = $this->editor->extendModel($filePath);
+ }
+ elseif($action == 'extendControl')
+ {
+ $okUrl = $this->editor->getExtendLink($filePath, 'extendControl', 'yes');
+ $cancelUrl = $this->editor->getExtendLink($filePath, 'extendControl', 'no');
+ if(!$isExtends) die(js::confirm($this->lang->editor->extendConfirm, $okUrl, $cancelUrl));
+ $fileContent = $this->editor->extendControl($filePath, $isExtends);
+ }
+ elseif($action == 'newPage')
+ {
+ $fileContent = $this->editor->newControl($filePath);
+ }
+ elseif(strrpos(basename($filePath), '.php') !== false and empty($fileContent))
+ {
+ $fileContent = "view->fileContent = $fileContent;
+ $this->view->filePath = $filePath;
+ $this->view->action = $action;
+ $this->display();
+ }
+
+ /**
+ * Set Page name.
+ *
+ * @param string $filePath
+ * @access public
+ * @return void
+ */
+ public function newPage($filePath)
+ {
+ $filePath = helper::safe64Decode($filePath);
+ if($_POST)
+ {
+ $saveFilePath = $this->editor->getSavePath($filePath, 'newMethod');
+ $extendLink = $this->editor->getExtendLink($saveFilePath, 'newPage');
+ if(file_exists($saveFilePath) and !$this->post->override) die(js::confirm($this->lang->editor->repeatPage, $extendLink, '', 'parent'));
+ die(js::locate($extendLink, 'parent'));
+ }
+ $this->view->filePath = $filePath;
+ $this->display();
+ }
+
+ /**
+ * Save file to extension.
+ *
+ * @param string $filePath
+ * @access public
+ * @return void
+ */
+ public function save($filePath = '', $action = '')
+ {
+ $statusFile = $this->loadModel('common')->checkSafeFile();
+ if($statusFile)
+ {
+ die(js::alert(sprintf($this->lang->editor->noticeOkFile, str_replace('\\', '/', $statusFile))));
+ }
+ if($filePath and $_POST)
+ {
+ $filePath = helper::safe64Decode($filePath);
+ if(strpos(strtolower($filePath), strtolower($this->app->getBasePath())) !== 0) die($this->lang->editor->editFileError);
+ if($action != 'edit' and $action != 'newPage') $filePath = $this->editor->getSavePath($filePath, $action);
+ if($action != 'edit' and $action != 'newPage' and file_exists($filePath) and !$this->post->override) die(js::error($this->lang->editor->repeatFile));
+ $this->editor->save($filePath);
+ echo js::reload('parent.parent.extendWin');
+ die(js::locate(inlink('edit', "filePath=" . helper::safe64Encode($filePath) . "&action=edit"), 'parent'));
+ }
+ }
+
+ /**
+ * Delete extension file.
+ *
+ * @param string $filePath
+ * @param string $confirm
+ * @access public
+ * @return void
+ */
+ public function delete($filePath = '', $confirm = 'no')
+ {
+ if($confirm == 'no')
+ {
+ die(js::confirm($this->lang->editor->deleteConfirm, inlink('delete', "filePath=$filePath&confirm=yes")));
+ }
+ $filePath = helper::safe64Decode($filePath);
+ if(file_exists($filePath) and unlink($filePath)) die(js::reload('parent'));
+ die(js::alert($this->lang->editor->notDelete));
+
+ }
+
+ public function switch($status)
+ {
+ $this->loadModel('setting')->setItem('system.common.global.editor', $status);
+
+ $link = empty($status) ? $this->createLink('dev', 'editor') : $this->createLink('editor', 'index');
+ return print(js::locate($link, 'parent'));
+ }
+}
diff --git a/module/editor/css/edit.css b/module/editor/css/edit.css
new file mode 100644
index 0000000000..93e062ed52
--- /dev/null
+++ b/module/editor/css/edit.css
@@ -0,0 +1,2 @@
+body{padding-bottom:0px;background-color:#fff;}
+#showContentEditor, #fileContentEditor {border:1px solid #ddd;}
diff --git a/module/editor/css/extend.css b/module/editor/css/extend.css
new file mode 100644
index 0000000000..e019a90efa
--- /dev/null
+++ b/module/editor/css/extend.css
@@ -0,0 +1,5 @@
+body{padding-bottom:0px;background-color:#fff;}
+.expandable > ul {display: none!important}
+.collapsable > ul {display: block!important}
+.tree li:before {margin-right: 4px;}
+.tree li>a{display:inline;}
diff --git a/module/editor/css/index.css b/module/editor/css/index.css
new file mode 100644
index 0000000000..4e5c35a06b
--- /dev/null
+++ b/module/editor/css/index.css
@@ -0,0 +1,7 @@
+table td {padding: 0 10px; vertical-align: top;}
+table tr > td:first-child {padding-left: 0;}
+table tr > td:last-child {padding-right: 0;}
+.modulegroup {font-weight:bold; padding-top:8px; border-top:1px solid #ddd;}
+td.w-200px a:hover {font-weight:bold;}
+.w-350px{width:350px;}
+.module-col{padding-bottom:30px;}
diff --git a/module/editor/js/edit.js b/module/editor/js/edit.js
new file mode 100644
index 0000000000..6e853d71c2
--- /dev/null
+++ b/module/editor/js/edit.js
@@ -0,0 +1 @@
+$(function(){ })
diff --git a/module/editor/js/index.js b/module/editor/js/index.js
new file mode 100644
index 0000000000..c2b1ddc184
--- /dev/null
+++ b/module/editor/js/index.js
@@ -0,0 +1,12 @@
+$(function()
+{
+ var showHeight = $('#main').height() - $('#mainMenu').height() - 40;
+ $('#editWin').height(showHeight);
+ $('#extendWin').height(showHeight);
+
+ $('.side-col a').click(function()
+ {
+ $('.side-col a.active').removeClass('active');
+ $(this).addClass('active');
+ });
+});
diff --git a/module/editor/lang/de.php b/module/editor/lang/de.php
new file mode 100644
index 0000000000..624035bbe1
--- /dev/null
+++ b/module/editor/lang/de.php
@@ -0,0 +1,76 @@
+editor->common = 'Editor';
+$lang->editor->api = 'API';
+$lang->editor->index = 'Home';
+$lang->editor->newMethod = 'Neue Methode';
+$lang->editor->extend = 'Erweiterung';
+$lang->editor->api = 'API';
+$lang->editor->newLang = 'Neue Sprache';
+$lang->editor->newConfig = 'Neue Konfiguration';
+$lang->editor->newHook = 'Neuer Hook';
+$lang->editor->newExtend = 'Neue Erweiterung';
+$lang->editor->newPage = 'Neue Seite';
+$lang->editor->override = 'Überschreiben';
+$lang->editor->edit = 'Erweiterung bearbeiten';
+$lang->editor->save = 'Seite speichern';
+$lang->editor->delete = 'Seite löschen';
+
+$lang->editor->moduleList = 'Module';
+$lang->editor->filePath = "Erweiterung:";
+$lang->editor->sourceFile = 'Source Datei:';
+$lang->editor->fileName = "Dateiname:";
+$lang->editor->isOverride = "Dublette überschrieben";
+$lang->editor->exampleHook = "(z.B. ***.html.hook.php)";
+$lang->editor->exampleJs = "(z.B. ***.js)";
+$lang->editor->exampleCss = "(z.B. ***.css)";
+$lang->editor->examplePHP = "(z.B. ***.php)";
+$lang->editor->pageName = "Seitenname:";
+
+$lang->editor->deleteConfirm = 'Möchten Sie das löschen?';
+$lang->editor->extendConfirm = 'Möchten Sie den Originalcode verwenden?';
+$lang->editor->repeatFile = 'Datei existiert bereits';
+$lang->editor->repeatPage = 'Seite existiert bereits. Möchten Sie die Seite überschreiben?';
+$lang->editor->noticeOkFile = 'Aus Sicherheitsgründen muss Ihr Adminkonto bestätigt werden. \n Bitte melden Sie sich an und erstellen Sie die Datei %s File.\n Hinweis:\n 1. Die Datei ist leer.\n 2. Wenn die Datei bereits existiert, löschen Sie sie und erstellen Sie eine neue Datei.';
+$lang->editor->editFileError = 'Sie können nur ZenTao Dateien verändern!';
+
+$lang->editor->notWritable = "Nicht beschreibbar. Berechtigung erforderlich. Bitte versuchen Sie chmod 777 -R ";
+$lang->editor->notDelete = 'Kann nicht gelöscht werden. Bitte prüfen Sie Ihre Berechtigungen!';
+$lang->editor->emptyFileName = 'Bitte geben Sie einen Dateinamen an!';
+$lang->editor->onlyLocalVisit = "This feature only works when the IP is 127.0.0.1. Help";
+
+$lang->editor->translate['config.php'] = 'config';
+$lang->editor->translate['control.php'] = 'control';
+$lang->editor->translate['model.php'] = 'model';
+
+$lang->editor->modules['action'] = 'System Log';
+$lang->editor->modules['admin'] = 'Admin';
+$lang->editor->modules['api'] = 'API';
+$lang->editor->modules['bug'] = 'Bug';
+$lang->editor->modules['build'] = 'Build';
+$lang->editor->modules['common'] = 'Bekannte Module';
+$lang->editor->modules['company'] = 'Unternehmen';
+$lang->editor->modules['convert'] = 'Konvertieren';
+$lang->editor->modules['dept'] = 'Abteilung';
+$lang->editor->modules['doc'] = 'Dok';
+$lang->editor->modules['extension'] = 'Erweiterung';
+$lang->editor->modules['file'] = 'Datei';
+$lang->editor->modules['group'] = 'Gruppe';
+$lang->editor->modules['index'] = 'Homepage';
+$lang->editor->modules['install'] = 'Installieren';
+$lang->editor->modules['misc'] = 'Sonstiges';
+$lang->editor->modules['my'] = 'Dashboard';
+$lang->editor->modules['product'] = $lang->productCommon;
+$lang->editor->modules['productplan'] = 'Plan';
+$lang->editor->modules['project'] = $lang->projectCommon;
+$lang->editor->modules['qa'] = 'QA';
+$lang->editor->modules['release'] = 'Release';
+$lang->editor->modules['report'] = 'Berichte';
+$lang->editor->modules['search'] = 'Suche';
+$lang->editor->modules['story'] = 'Story';
+$lang->editor->modules['task'] = 'Aufgaben';
+$lang->editor->modules['testcase'] = 'Fälle';
+$lang->editor->modules['testtask'] = 'Testaufgaben';
+$lang->editor->modules['todo'] = 'Todos';
+$lang->editor->modules['tree'] = 'Module';
+$lang->editor->modules['upgrade'] = 'Upgrade';
+$lang->editor->modules['user'] = 'Benutzer';
diff --git a/module/editor/lang/en.php b/module/editor/lang/en.php
new file mode 100644
index 0000000000..5dfb681829
--- /dev/null
+++ b/module/editor/lang/en.php
@@ -0,0 +1,77 @@
+editor->common = 'Editor';
+$lang->editor->api = 'API';
+$lang->editor->index = 'Home';
+$lang->editor->newMethod = 'New Method';
+$lang->editor->extend = 'Extension';
+$lang->editor->api = 'API';
+$lang->editor->newLang = 'New Language';
+$lang->editor->newConfig = 'New Config';
+$lang->editor->newHook = 'New Hook';
+$lang->editor->newExtend = 'New Extension';
+$lang->editor->newPage = 'New Page';
+$lang->editor->override = 'Overwrite';
+$lang->editor->edit = 'Edit Extension';
+$lang->editor->save = 'Save Page';
+$lang->editor->delete = 'Delete Page';
+
+$lang->editor->moduleList = 'Module';
+$lang->editor->filePath = "Extension:";
+$lang->editor->sourceFile = 'Source File:';
+$lang->editor->fileName = "File Name:";
+$lang->editor->isOverride = "Override Duplicated File";
+$lang->editor->exampleHook = "(e.g. ***.html.hook.php)";
+$lang->editor->exampleJs = "(e.g. ***.js)";
+$lang->editor->exampleCss = "(e.g. ***.css)";
+$lang->editor->examplePHP = "(e.g. ***.php)";
+$lang->editor->pageName = "Page Name:";
+
+$lang->editor->deleteConfirm = 'Do you want to delete it?';
+$lang->editor->extendConfirm = 'Do you want to use original code?';
+$lang->editor->repeatFile = 'File Name Duplicated';
+$lang->editor->repeatPage = 'This page exists. Do you want to overwrite it?';
+$lang->editor->noticeOkFile = 'For security reasons, your Admin account has to be confirmed. \n Please login ZenTao server and create %s File.\n Note:\n 1. File is blank.\n 2. If the file existed, delete it and then create a new one.';
+$lang->editor->editFileError = 'You can only modify ZenTao files!';
+
+$lang->editor->notWritable = "Not wirtable. Permission is required. Please try chmod 777 -R ";
+$lang->editor->notDelete = 'Cannot be deleted. Please check your permissions!';
+$lang->editor->emptyFileName = 'Please enter a File Name!';
+$lang->editor->onlyLocalVisit = "This feature only works when the IP is 127.0.0.1 for security. Help";
+
+$lang->editor->translate['config.php'] = 'config';
+$lang->editor->translate['control.php'] = 'control';
+$lang->editor->translate['model.php'] = 'model';
+
+$lang->editor->modules['action'] = 'System Log';
+$lang->editor->modules['admin'] = 'Admin';
+$lang->editor->modules['api'] = 'API';
+$lang->editor->modules['bug'] = 'Bug';
+$lang->editor->modules['build'] = 'Build';
+$lang->editor->modules['common'] = 'Common Module';
+$lang->editor->modules['company'] = 'Company';
+$lang->editor->modules['convert'] = 'Convert';
+$lang->editor->modules['dept'] = 'Department';
+$lang->editor->modules['doc'] = 'Doc';
+$lang->editor->modules['extension'] = 'Extension';
+$lang->editor->modules['file'] = 'File';
+$lang->editor->modules['group'] = 'Group';
+$lang->editor->modules['index'] = 'Homepage';
+$lang->editor->modules['install'] = 'Install';
+$lang->editor->modules['misc'] = 'Misc';
+$lang->editor->modules['my'] = 'Dashboard';
+$lang->editor->modules['product'] = $lang->productCommon;
+$lang->editor->modules['productplan'] = 'Plan';
+$lang->editor->modules['project'] = $lang->projectCommon;
+$lang->editor->modules['execution'] = $lang->executionCommon;
+$lang->editor->modules['qa'] = 'QA';
+$lang->editor->modules['release'] = 'Release';
+$lang->editor->modules['report'] = 'Report';
+$lang->editor->modules['search'] = 'Search';
+$lang->editor->modules['story'] = $lang->URCommon;
+$lang->editor->modules['task'] = 'Task';
+$lang->editor->modules['testcase'] = 'Case';
+$lang->editor->modules['testtask'] = 'Request';
+$lang->editor->modules['todo'] = 'Todo';
+$lang->editor->modules['tree'] = 'Module Maintenance';
+$lang->editor->modules['upgrade'] = 'Upgrade';
+$lang->editor->modules['user'] = 'User';
diff --git a/module/editor/lang/fr.php b/module/editor/lang/fr.php
new file mode 100644
index 0000000000..d483fce164
--- /dev/null
+++ b/module/editor/lang/fr.php
@@ -0,0 +1,76 @@
+editor->common = 'Editeur';
+$lang->editor->api = 'API';
+$lang->editor->index = 'Accueil';
+$lang->editor->newMethod = 'New Méthode';
+$lang->editor->extend = 'Extension';
+$lang->editor->api = 'API';
+$lang->editor->newLang = 'New Language';
+$lang->editor->newConfig = 'New Config';
+$lang->editor->newHook = 'New Hook';
+$lang->editor->newExtend = 'New Extension';
+$lang->editor->newPage = 'New Page';
+$lang->editor->override = 'Ecraser';
+$lang->editor->edit = 'Editer Extension';
+$lang->editor->save = 'Sauver Page';
+$lang->editor->delete = 'Supprimer Page';
+
+$lang->editor->moduleList = 'Module';
+$lang->editor->filePath = "Extension:";
+$lang->editor->sourceFile = 'Fichier Source :';
+$lang->editor->fileName = "Nom Fichier :";
+$lang->editor->isOverride = "Override Duplicated File";
+$lang->editor->exampleHook = "(e.g. ***.html.hook.php)";
+$lang->editor->exampleJs = "(e.g. ***.js)";
+$lang->editor->exampleCss = "(e.g. ***.css)";
+$lang->editor->examplePHP = "(e.g. ***.php)";
+$lang->editor->pageName = "Nom Page :";
+
+$lang->editor->deleteConfirm = 'Voulez-vous le supprimer ?';
+$lang->editor->extendConfirm = 'Voulez-vous utiliser le code original ?';
+$lang->editor->repeatFile = 'Doublon';
+$lang->editor->repeatPage = "Cette page existe. Voulez-vous l'écraser ?";
+$lang->editor->noticeOkFile = 'Pour des raisons de sécurité, votre compte administrateur doit être confirmé. \n Please login ZenTao server and create %s File.\n Note:\n 1. File is blank.\n 2. If the file existed, delete it and then create a new one.';
+$lang->editor->editFileError = 'Vous pouvez seulement modifier les fichiers ZenTao !';
+
+$lang->editor->notWritable = "Non inscriptible. Permission requise. Essayez chmod 777 -R ";
+$lang->editor->notDelete = 'Ne peut pas être supprimé. Vérifiez les permissions !';
+$lang->editor->emptyFileName = 'Entrez un nom de fichier !';
+$lang->editor->onlyLocalVisit = "Cette fonctionnalité ne fonctionne que pour une IP 127.0.0.1. Aide";
+
+$lang->editor->translate['config.php'] = 'config';
+$lang->editor->translate['control.php'] = 'contrôle';
+$lang->editor->translate['model.php'] = 'modèle';
+
+$lang->editor->modules['action'] = 'System Log';
+$lang->editor->modules['admin'] = 'Admin';
+$lang->editor->modules['api'] = 'API';
+$lang->editor->modules['bug'] = 'Bug';
+$lang->editor->modules['build'] = 'Build';
+$lang->editor->modules['common'] = 'Module Commun';
+$lang->editor->modules['company'] = 'Entreprise';
+$lang->editor->modules['convert'] = 'Convertir';
+$lang->editor->modules['dept'] = 'Compartiment';
+$lang->editor->modules['doc'] = 'Doc';
+$lang->editor->modules['extension'] = 'Extension';
+$lang->editor->modules['file'] = 'Fichier';
+$lang->editor->modules['group'] = 'Groupe';
+$lang->editor->modules['index'] = 'Accueil';
+$lang->editor->modules['install'] = 'Installer';
+$lang->editor->modules['misc'] = 'Divers';
+$lang->editor->modules['my'] = 'Dashboard';
+$lang->editor->modules['product'] = $lang->productCommon;
+$lang->editor->modules['productplan'] = 'Plan';
+$lang->editor->modules['project'] = $lang->projectCommon;
+$lang->editor->modules['qa'] = 'QA';
+$lang->editor->modules['release'] = 'Release';
+$lang->editor->modules['report'] = 'Rapport';
+$lang->editor->modules['search'] = 'Recherche';
+$lang->editor->modules['story'] = 'Story';
+$lang->editor->modules['task'] = 'Tâche';
+$lang->editor->modules['testcase'] = 'CasTest';
+$lang->editor->modules['testtask'] = 'Recette';
+$lang->editor->modules['todo'] = 'Agenda';
+$lang->editor->modules['tree'] = 'Module Maintenance';
+$lang->editor->modules['upgrade'] = 'Upgrade';
+$lang->editor->modules['user'] = 'User';
diff --git a/module/editor/lang/vi.php b/module/editor/lang/vi.php
new file mode 100644
index 0000000000..80b792e7a7
--- /dev/null
+++ b/module/editor/lang/vi.php
@@ -0,0 +1,76 @@
+editor->common = 'Editor';
+$lang->editor->api = 'API';
+$lang->editor->index = 'Home';
+$lang->editor->newMethod = 'New Method';
+$lang->editor->extend = 'Extension';
+$lang->editor->api = 'API';
+$lang->editor->newLang = 'Ngôn ngữ mới';
+$lang->editor->newConfig = 'Cấu hình mới';
+$lang->editor->newHook = 'New Hook';
+$lang->editor->newExtend = 'New Extension';
+$lang->editor->newPage = 'Trang mới';
+$lang->editor->override = 'Ghi đè';
+$lang->editor->edit = 'Edit Extension';
+$lang->editor->save = 'Save Page';
+$lang->editor->delete = 'Delete Page';
+
+$lang->editor->moduleList = 'Module';
+$lang->editor->filePath = "Extension:";
+$lang->editor->sourceFile = 'Nguồn File:';
+$lang->editor->fileName = "File Name:";
+$lang->editor->isOverride = "Override Duplicated File";
+$lang->editor->exampleHook = "(ví dụ: ***.html.hook.php)";
+$lang->editor->exampleJs = "(ví dụ: ***.js)";
+$lang->editor->exampleCss = "(ví dụ: ***.css)";
+$lang->editor->examplePHP = "(ví dụ: ***.php)";
+$lang->editor->pageName = "Tên trang:";
+
+$lang->editor->deleteConfirm = 'Bạn có muốn xóa nó?';
+$lang->editor->extendConfirm = 'Do you want to use original code?';
+$lang->editor->repeatFile = 'File Name Duplicated';
+$lang->editor->repeatPage = 'This page exists. Do you want to overwrite it?';
+$lang->editor->noticeOkFile = 'For security reasons, your Admin account has to be confirmed. \n Please login ZenTao server and create %s File.\n Note:\n 1. File is blank.\n 2. If the file existed, delete it and then create a new one.';
+$lang->editor->editFileError = 'Bạn có thể only modify ZenTao files!';
+
+$lang->editor->notWritable = "Not wirtable. Permission là bắt buộc. Please try chmod 777 -R ";
+$lang->editor->notDelete = 'Không thể be deleted. Please check your permissions!';
+$lang->editor->emptyFileName = 'Please enter a File Name!';
+$lang->editor->onlyLocalVisit = "This feature only works when the IP is 127.0.0.1 for security. Giúp";
+
+$lang->editor->translate['config.php'] = 'config';
+$lang->editor->translate['control.php'] = 'control';
+$lang->editor->translate['model.php'] = 'model';
+
+$lang->editor->modules['action'] = 'Nhật ký hệ thống';
+$lang->editor->modules['admin'] = 'Quản trị';
+$lang->editor->modules['api'] = 'API';
+$lang->editor->modules['bug'] = 'Bug';
+$lang->editor->modules['build'] = 'Bản dựng';
+$lang->editor->modules['common'] = 'Module chung';
+$lang->editor->modules['company'] = 'Doanh nghiệp';
+$lang->editor->modules['convert'] = 'Chuyển đổi';
+$lang->editor->modules['dept'] = 'Phòng/Ban';
+$lang->editor->modules['doc'] = 'Tài liệu';
+$lang->editor->modules['extension'] = 'Extension';
+$lang->editor->modules['file'] = 'File';
+$lang->editor->modules['group'] = 'Nhóm';
+$lang->editor->modules['index'] = 'Homepage';
+$lang->editor->modules['install'] = 'Cài đặt';
+$lang->editor->modules['misc'] = 'Khác';
+$lang->editor->modules['my'] = 'Dashboard';
+$lang->editor->modules['product'] = $lang->productCommon;
+$lang->editor->modules['productplan'] = 'Kế hoạch';
+$lang->editor->modules['project'] = $lang->projectCommon;
+$lang->editor->modules['qa'] = 'QA';
+$lang->editor->modules['release'] = 'Phát hành';
+$lang->editor->modules['report'] = 'Báo cáo';
+$lang->editor->modules['search'] = 'Tìm kiếm';
+$lang->editor->modules['story'] = 'Câu chuyện';
+$lang->editor->modules['task'] = 'Nhiệm vụ';
+$lang->editor->modules['testcase'] = 'Tình huống';
+$lang->editor->modules['testtask'] = 'Yêu cầu';
+$lang->editor->modules['todo'] = 'Việc làm';
+$lang->editor->modules['tree'] = 'Bảo trì';
+$lang->editor->modules['upgrade'] = 'Nâng cấp';
+$lang->editor->modules['user'] = 'Người dùng';
diff --git a/module/editor/lang/zh-cn.php b/module/editor/lang/zh-cn.php
new file mode 100644
index 0000000000..82898da78e
--- /dev/null
+++ b/module/editor/lang/zh-cn.php
@@ -0,0 +1,78 @@
+editor->common = '编辑器';
+$lang->editor->api = 'API';
+$lang->editor->index = '首页';
+$lang->editor->newMethod = '新增方法';
+$lang->editor->extend = '扩展';
+$lang->editor->api = 'API';
+$lang->editor->newLang = '新增语言';
+$lang->editor->newConfig = '新增配置';
+$lang->editor->newHook = '新增钩子';
+$lang->editor->newExtend = '新增扩展';
+$lang->editor->newPage = '新增页面';
+$lang->editor->override = '覆盖';
+$lang->editor->edit = '编辑扩展';
+$lang->editor->save = '保存页面';
+$lang->editor->delete = '删除页面';
+
+$lang->editor->moduleList = '模块列表';
+$lang->editor->filePath = "扩展:";
+$lang->editor->sourceFile = '源文件:';
+$lang->editor->fileName = "文件名:";
+$lang->editor->isOverride = "覆盖重复文件";
+$lang->editor->exampleHook = "(例如:***.html.hook.php)";
+$lang->editor->exampleJs = "(例如:***.js)";
+$lang->editor->exampleCss = "(例如:***.css)";
+$lang->editor->examplePHP = "(例如:***.php)";
+$lang->editor->pageName = "页面名称:";
+
+$lang->editor->deleteConfirm = '是否要删除?';
+$lang->editor->extendConfirm = '是否要重用原来代码?';
+$lang->editor->repeatFile = '文件名重复';
+$lang->editor->repeatPage = '已经有此页面。是否覆盖?';
+$lang->editor->noticeOkFile = '为了安全起见,系统需要确认您的管理员身份。\n 请登录禅道所在的服务器,创建%s文件。\n 注意:\n 1. 文件内容为空。\n 2. 如果之前文件存在,删除之后重新创建。';
+$lang->editor->editFileError = '只能修改禅道文件!';
+$lang->editor->turnOff = '如不使用编辑器功能,可在此关闭。';
+
+$lang->editor->notWritable = "无法写入,可能没有权限。请尝试执行 chmod 777 -R ";
+$lang->editor->notDelete = '无法删除,请检查权限!';
+$lang->editor->emptyFileName = '请写入一个文件名!';
+$lang->editor->onlyLocalVisit = "该功能只能在IP:127.0.0.1下访问。说明文档";
+
+$lang->editor->translate['config.php'] = 'config';
+$lang->editor->translate['control.php'] = 'control';
+$lang->editor->translate['model.php'] = 'model';
+
+$lang->editor->modules['action'] = '系统日志';
+$lang->editor->modules['admin'] = '后台管理';
+$lang->editor->modules['api'] = 'API接口';
+$lang->editor->modules['bug'] = 'Bug管理';
+$lang->editor->modules['build'] = 'Build';
+$lang->editor->modules['common'] = '公有模块';
+$lang->editor->modules['company'] = '组织视图';
+$lang->editor->modules['convert'] = '从其他系统导入';
+$lang->editor->modules['dept'] = '部门结构';
+$lang->editor->modules['doc'] = '文档视图';
+$lang->editor->modules['extension'] = '插件管理';
+$lang->editor->modules['file'] = '附件';
+$lang->editor->modules['group'] = '权限分组';
+$lang->editor->modules['index'] = '首页';
+$lang->editor->modules['install'] = '安装';
+$lang->editor->modules['misc'] = '杂项';
+$lang->editor->modules['my'] = '我的地盘';
+$lang->editor->modules['product'] = $lang->productCommon . '视图';
+$lang->editor->modules['productplan'] = '计划';
+$lang->editor->modules['project'] = '项目视图';
+$lang->editor->modules['execution'] = $lang->executionCommon . '视图';
+$lang->editor->modules['qa'] = '测试视图';
+$lang->editor->modules['release'] = '发布';
+$lang->editor->modules['report'] = '报表';
+$lang->editor->modules['search'] = '搜索';
+$lang->editor->modules['story'] = $lang->URCommon;
+$lang->editor->modules['task'] = '任务';
+$lang->editor->modules['testcase'] = '用例管理';
+$lang->editor->modules['testtask'] = '测试单';
+$lang->editor->modules['todo'] = '待办';
+$lang->editor->modules['tree'] = '模块维护';
+$lang->editor->modules['upgrade'] = '升级';
+$lang->editor->modules['user'] = '用户';
diff --git a/module/editor/lang/zh-tw.php b/module/editor/lang/zh-tw.php
new file mode 100644
index 0000000000..b1308fe75b
--- /dev/null
+++ b/module/editor/lang/zh-tw.php
@@ -0,0 +1,76 @@
+editor->common = '編輯器';
+$lang->editor->api = 'API';
+$lang->editor->index = '首頁';
+$lang->editor->newMethod = '新增方法';
+$lang->editor->extend = '擴展';
+$lang->editor->api = 'API';
+$lang->editor->newLang = '新增語言';
+$lang->editor->newConfig = '新增配置';
+$lang->editor->newHook = '新增鈎子';
+$lang->editor->newExtend = '新增擴展';
+$lang->editor->newPage = '新增頁面';
+$lang->editor->override = '覆蓋';
+$lang->editor->edit = '編輯擴展';
+$lang->editor->save = '保存頁面';
+$lang->editor->delete = '刪除頁面';
+
+$lang->editor->moduleList = '模組列表';
+$lang->editor->filePath = "擴展:";
+$lang->editor->sourceFile = '源檔案:';
+$lang->editor->fileName = "檔案名:";
+$lang->editor->isOverride = "覆蓋重複檔案";
+$lang->editor->exampleHook = "(例如:***.html.hook.php)";
+$lang->editor->exampleJs = "(例如:***.js)";
+$lang->editor->exampleCss = "(例如:***.css)";
+$lang->editor->examplePHP = "(例如:***.php)";
+$lang->editor->pageName = "頁面名稱:";
+
+$lang->editor->deleteConfirm = '是否要刪除?';
+$lang->editor->extendConfirm = '是否要重用原來代碼?';
+$lang->editor->repeatFile = '檔案名重複';
+$lang->editor->repeatPage = '已經有此頁面。是否覆蓋?';
+$lang->editor->noticeOkFile = '為了安全起見,系統需要確認您的管理員身份。\n 請登錄禪道所在的伺服器,創建%s檔案。\n 注意:\n 1. 檔案內容為空。\n 2. 如果之前檔案存在,刪除之後重新創建。';
+$lang->editor->editFileError = '只能修改禪道檔案!';
+
+$lang->editor->notWritable = "無法寫入,可能沒有權限。請嘗試執行 chmod 777 -R ";
+$lang->editor->notDelete = '無法刪除,請檢查權限!';
+$lang->editor->emptyFileName = '請寫入一個檔案名!';
+$lang->editor->onlyLocalVisit = "該功能只能在IP:127.0.0.1下訪問。說明文檔";
+
+$lang->editor->translate['config.php'] = 'config';
+$lang->editor->translate['control.php'] = 'control';
+$lang->editor->translate['model.php'] = 'model';
+
+$lang->editor->modules['action'] = '系統日誌';
+$lang->editor->modules['admin'] = '後台管理';
+$lang->editor->modules['api'] = 'API介面';
+$lang->editor->modules['bug'] = 'Bug管理';
+$lang->editor->modules['build'] = 'Build';
+$lang->editor->modules['common'] = '公有模組';
+$lang->editor->modules['company'] = '組織視圖';
+$lang->editor->modules['convert'] = '從其他系統導入';
+$lang->editor->modules['dept'] = '部門結構';
+$lang->editor->modules['doc'] = '文檔視圖';
+$lang->editor->modules['extension'] = '插件管理';
+$lang->editor->modules['file'] = '附件';
+$lang->editor->modules['group'] = '權限分組';
+$lang->editor->modules['index'] = '首頁';
+$lang->editor->modules['install'] = '安裝';
+$lang->editor->modules['misc'] = '雜項';
+$lang->editor->modules['my'] = '我的地盤';
+$lang->editor->modules['product'] = $lang->productCommon . '視圖';
+$lang->editor->modules['productplan'] = '計劃';
+$lang->editor->modules['project'] = $lang->projectCommon . '視圖';
+$lang->editor->modules['qa'] = '測試視圖';
+$lang->editor->modules['release'] = '發佈';
+$lang->editor->modules['report'] = '報表';
+$lang->editor->modules['search'] = '搜索';
+$lang->editor->modules['story'] = $lang->storyCommon;
+$lang->editor->modules['task'] = '任務';
+$lang->editor->modules['testcase'] = '用例管理';
+$lang->editor->modules['testtask'] = '測試單';
+$lang->editor->modules['todo'] = '待辦';
+$lang->editor->modules['tree'] = '模組維護';
+$lang->editor->modules['upgrade'] = '升級';
+$lang->editor->modules['user'] = '用戶';
diff --git a/module/editor/model.php b/module/editor/model.php
new file mode 100644
index 0000000000..9caf1e4ce1
--- /dev/null
+++ b/module/editor/model.php
@@ -0,0 +1,539 @@
+
+ * @package editor
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+class editorModel extends model
+{
+ /**
+ * Get all modules
+ *
+ * @access public
+ * @return string
+ */
+ public function getModules()
+ {
+ $modules = glob($this->app->getModuleRoot() . '*');
+ $moduleList = "";
+ foreach($modules as $module)
+ {
+ $module = basename($module);
+ if($module == 'editor' or $module == 'help' or $module == 'setting') continue;
+ $moduleName = !empty($this->lang->editor->modules[$module]) ? $this->lang->editor->modules[$module] : $module;
+ $moduleList .= html::a(inlink('extend', "moduleDir=$module"), $moduleName, 'extendWin', "class='list-group-item'");
+ }
+ $moduleList .= '
';
+ return $moduleList;
+ }
+
+ /**
+ * Get module files, contain control's methods and model's method but except ext.
+ *
+ * @access public
+ * @return array
+ */
+ public function getModuleFiles($moduleName)
+ {
+ $allModules = array();
+ $modulePath = $this->app->getModulePath('', $moduleName);
+ foreach($this->config->editor->sort as $name)
+ {
+ $moduleFullFile = $modulePath . $name;
+ if($name == 'control.php' or $name == 'model.php')
+ {
+ $allModules[$modulePath][$moduleFullFile] = $this->analysis($moduleFullFile);
+ }
+ elseif(is_dir($moduleFullFile))
+ {
+ $ext = ($name == 'js' or $name == 'css') ? $name : 'php';
+ foreach(glob($moduleFullFile . DS . "*.$ext") as $fileName) $allModules[$modulePath][$moduleFullFile][$fileName] = basename($fileName);
+ }
+ else
+ {
+ $allModules[$modulePath][$moduleFullFile] = $name;
+ }
+ }
+ $allModules += $this->getExtensionFiles($moduleName);
+ return $allModules;
+ }
+
+ /**
+ * Get extension files.
+ *
+ * @param int $extPath
+ * @access public
+ * @return void
+ */
+ public function getExtensionFiles($moduleName)
+ {
+ $extensionList = array();
+ foreach($this->config->editor->extSort as $ext)
+ {
+ $extModulePaths = $this->app->getModuleExtPath('', $moduleName, $ext);
+ foreach($extModulePaths as $extensionFullDir)
+ {
+ if(!is_dir($extensionFullDir)) continue;
+
+ if($ext == 'lang' or $ext == 'js' or $ext == 'css')
+ {
+ $extensionList[$extensionFullDir] = $this->getTwoGradeFiles($extensionFullDir);
+ continue;
+ }
+ foreach(glob($extensionFullDir . '*') as $extensionFullFile) $extensionList[$extensionFullDir][$extensionFullFile] = basename($extensionFullFile);
+ }
+ }
+ return $extensionList;
+ }
+
+ /**
+ * if a directory has two grage, this method will get files
+ *
+ * @param string $extensionFullDir
+ * @access public
+ * @return string
+ */
+ public function getTwoGradeFiles($extensionFullDir)
+ {
+ $fileList = array();
+ $langDirs = scandir($extensionFullDir);
+ foreach($langDirs as $langDir)
+ {
+ if($langDir == '.' or $langDir == '..' or $langDir == '.svn') continue;
+ $langFullDir = $extensionFullDir . DS . $langDir;
+ $fileList[$langFullDir] = array();
+ if(is_dir($langFullDir))
+ {
+ $langFiles = scandir($langFullDir);
+ foreach($langFiles as $langFile)
+ {
+ if($langFile == '.' or $langFile == '..' or $langFile == '.svn') continue;
+ $langFullFile = $langFullDir . DS . $langFile;
+ $fileList[$langFullDir][$langFullFile] = $langFile;
+ }
+ }
+ }
+ return $fileList;
+ }
+
+ /**
+ * Analysis methods of control and model.
+ *
+ * @param string $fileName
+ * @access public
+ * @return array
+ */
+ public function analysis($fileName)
+ {
+ $classMethod = array();
+ $class = strstr($fileName, DS . 'module' . DS);
+ if(empty($class))
+ {
+ $class = strstr($fileName, DS . 'extension' . DS);
+ $class = str_replace(DS . 'extension' . DS, '', $class);
+ $class = ltrim(strstr($class, DS), DS);
+ }
+ else
+ {
+ $class = str_replace(DS . 'module' . DS, '', $class);
+ }
+
+ $class = dirname($class);
+ if(strpos($fileName, 'model.php') !== false) $class .= 'Model';
+ if(!class_exists($class)) include $fileName;
+ $reflection = new ReflectionClass($class);
+ foreach($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method)
+ {
+ $methodName = $method->name;
+ if($method->getFileName() != $fileName) continue;
+ if($methodName == '__construct') continue;
+ $classMethod[$fileName . DS . $methodName] = $methodName;
+ }
+ return $classMethod;
+ }
+
+ /**
+ * Print tree from module files.
+ *
+ * @param int $files
+ * @access public
+ * @return void
+ */
+ public function printTree($files, $isRoot = true)
+ {
+ if(empty($files) or !is_array($files)) return false;
+ $tree = $isRoot ? "\n" : "\n";
+ if($isRoot)
+ {
+ $module = basename(dirname(key($files)));
+
+ $langFile = dirname(key($files)) . DS . 'lang' . DS . $this->cookie->lang. '.php';
+ if(file_exists($langFile))
+ {
+ if(!isset($lang)) $lang = $this->lang;
+ if(!isset($lang->$module)) $lang->$module = new stdclass();
+ include_once $langFile;
+ }
+
+ if(isset($lang->$module))
+ {
+ $this->module = $lang->$module;
+ }
+ elseif(isset($this->lang->$module))
+ {
+ $this->module = $this->lang->$module;
+ }
+ else
+ {
+ $this->module = '';
+ }
+ }
+ foreach($files as $key => $file)
+ {
+ $tree .= "- \n";
+ if(is_array($file))
+ {
+ $tree .= $this->addLink4Dir($key);
+ $tree .= $this->printTree($file, false);
+ }
+ else
+ {
+ $tree .= $this->addLink4File($key, $file);
+ }
+ $tree .= "
\n";
+ }
+ $tree .= "
\n";
+ return $tree;
+ }
+
+ /**
+ * Add link for directory or has children grade
+ *
+ * @param string $filePath
+ * @access public
+ * @return string
+ */
+ public function addLink4Dir($filePath)
+ {
+ $tree = '';
+ $fileName = basename($filePath);
+ if(isset($this->lang->editor->modules[$fileName]))
+ {
+ $file = "" . $this->lang->editor->modules[$fileName] . '';
+ }
+ elseif(isset($this->lang->editor->translate[$fileName]))
+ {
+ $file = "" . $this->lang->editor->translate[$fileName] . '';
+ }
+ else
+ {
+ $file = "$fileName";
+ }
+ if(strpos($filePath, DS . 'ext' . DS) !== false)
+ {
+ switch($fileName)
+ {
+ case 'lang': $tree .= $file; break;
+ case 'js': $tree .= "$file " . html::a($this->getExtendLink($filePath, "newJS"), $this->lang->editor->newExtend, 'editWin'); break;
+ case 'css': $tree .= "$file " . html::a($this->getExtendLink($filePath, "newCSS"), $this->lang->editor->newExtend, 'editWin'); break;
+ default: $tree .= "$file " . html::a($this->getExtendLink($filePath, "newExtend"), $this->lang->editor->newExtend, 'editWin');
+ }
+ }
+ elseif($fileName == 'model.php')
+ {
+ $tree .= "$file " . html::a($this->getExtendLink($filePath, 'newMethod'), $this->lang->editor->newMethod, 'editWin');
+ }
+ elseif($fileName == 'control.php')
+ {
+ $tree .= "$file " . html::a(inlink('newPage', "filePath=" . helper::safe64Encode($filePath)), $this->lang->editor->newPage, 'editWin');
+ }
+ else
+ {
+ $tree .= $file;
+ }
+ return $tree;
+ }
+
+ /**
+ * Add link for file
+ *
+ * @param string $filePath
+ * @param string $file
+ * @access public
+ * @return string
+ */
+ public function addLink4File($filePath, $file)
+ {
+ $tree = '';
+ $file = "$file";
+ if(strpos($filePath, DS . 'ext' . DS) !== false)
+ {
+ $tree .= "$file " . html::a($this->getExtendLink($filePath, "edit"), $this->lang->edit, 'editWin');
+ $tree .= html::a(inlink('delete', 'path=' . helper::safe64Encode($filePath)), $this->lang->delete, 'hiddenwin') . "\n";
+ }
+ elseif(basename(dirname($filePath))== 'view')
+ {
+ $tree .= "$file " . html::a($this->getExtendLink($filePath, "override"), $this->lang->editor->override, 'editWin');
+ $tree .= html::a($this->getExtendLink($filePath, "newHook"), $this->lang->editor->newHook, 'editWin') . "\n";
+ }
+ else
+ {
+ $parentDir = basename(dirname($filePath));
+ $action = 'extendOther';
+ if($parentDir == 'control.php') $action = 'extendControl';
+ if($parentDir == 'model.php') $action = 'extendModel';
+
+ $tree .= "$file " . html::a($this->getExtendLink($filePath, $action), $this->lang->editor->extend, 'editWin');
+ if($action != 'extendOther') $tree .= html::a($this->getAPILink($filePath, $action), $this->lang->editor->api, 'editWin');
+ if($parentDir == 'lang') $tree .= html::a($this->getExtendLink($filePath, "new" . str_replace('-', '_', basename($filePath, '.php'))), $this->lang->editor->newLang, 'editWin');
+ if(basename($filePath) == 'config.php') $tree .= html::a($this->getExtendLink($filePath, "newConfig"), $this->lang->editor->newConfig, 'editWin');
+ }
+ return $tree;
+ }
+
+ /**
+ * Get extend link
+ *
+ * @param string $filePath
+ * @param string $action
+ * @param string $isExtends
+ * @access public
+ * @return string
+ */
+ public function getExtendLink($filePath, $action, $isExtends = '')
+ {
+ return inlink('edit', "filePath=" . helper::safe64Encode($filePath) . "&action=$action&isExtends=$isExtends");
+ }
+
+ /**
+ * Get api link.
+ *
+ * @param int $filePath
+ * @param int $action
+ * @param string $type
+ * @access public
+ * @return string
+ */
+ public function getAPILink($filePath, $action)
+ {
+ return helper::createLink('api', 'debug', "filePath=" . helper::safe64Encode($filePath) . "&action=$action");
+ }
+
+ /**
+ * Save file to extension.
+ *
+ * @param string $filePath
+ * @access public
+ * @return void
+ */
+ public function save($filePath)
+ {
+ $statusFile = $this->loadModel('common')->checkSafeFile();
+ if($statusFile) die(js::alert(sprintf($this->lang->editor->noticeOkFile, str_replace('\\', '/', $statusFile))));
+
+ $dirPath = dirname($filePath);
+ $extFilePath = substr($filePath, 0, strpos($filePath, DS . 'ext' . DS) + 4);
+ if(!is_dir($dirPath) and is_writable($extFilePath)) mkdir($dirPath, 0777, true);
+ if(!is_dir($dirPath) or !is_writable($dirPath)) die(js::alert($this->lang->editor->notWritable . $extFilePath));
+ if(strpos(strtolower(realpath($dirPath)), strtolower($this->app->getBasePath())) !== 0) die(js::alert($this->lang->editor->editFileError));
+
+ $fileContent = $this->post->fileContent;
+ $evils = array('eval', 'exec', 'passthru', 'proc_open', 'shell_exec', 'system', '$$', 'include', 'require', 'assert', 'javascript', 'onclick');
+ $gibbedEvils = array('e v a l', 'e x e c', ' p a s s t h r u', ' p r o c _ o p e n', 's h e l l _ e x e c', 's y s t e m', '$ $', 'i n c l u d e', 'r e q u i r e', 'a s s e r t', 'j a v a s c r i p t', 'o n c l i c k');
+ $fileContent = str_ireplace($gibbedEvils, $evils, $fileContent);
+ if(get_magic_quotes_gpc()) $fileContent = stripslashes($fileContent);
+
+ file_put_contents($filePath, $fileContent);
+ }
+
+ /**
+ * Extend model.php and get file content.
+ *
+ * @param string $filePath
+ * @access public
+ * @return string
+ */
+ public function extendModel($filePath)
+ {
+ $className = basename(dirname(dirname($filePath)));
+ if(!class_exists($className)) helper::import(dirname($filePath));
+ $methodName = basename($filePath);
+ $methodParam = $this->getParam($className, $methodName, 'Model');
+ return $fileContent = <<getParam($className, $methodName);
+ return $fileContent = <<getMethodCode($className, $methodName);
+ return $fileContent = <<getParameters() as $param)
+ {
+ $methodParam .= '$' . $param->getName();
+ if($param->isOptional())
+ {
+ $defaultParam = $param->getDefaultValue();
+ if(is_string($defaultParam)) $methodParam .= "='$defaultParam', ";
+ else $methodParam .= "=$defaultParam, ";
+ }
+ else
+ {
+ $methodParam .= ', ';
+ }
+ }
+ $methodParam = rtrim($methodParam, ', ');
+ return $methodParam;
+ }
+
+ /**
+ * Get method code.
+ *
+ * @param string $className
+ * @param string $methodName
+ * @param string $ext value may be Model
+ * @access public
+ * @return string
+ */
+ public function getMethodCode($className, $methodName, $ext = '')
+ {
+ $method = new ReflectionMethod($className . $ext, $methodName);
+ $fileName = $method->getFileName();
+ $startLine = $method->getStartLine();
+ $endLine = $method->getEndLine();
+ $file = file($fileName);
+ $code = '';
+ for($i = $startLine - 1; $i <= $endLine; $i++)
+ {
+ $code .= $file[$i];
+ }
+ return $code;
+ }
+
+ /**
+ * Get save path.
+ *
+ * @param string $filePath
+ * @param string $action
+ * @access public
+ * @return string
+ */
+ public function getSavePath($filePath, $action)
+ {
+ $fileName = empty($_POST['fileName']) ? '' : trim($this->post->fileName);
+ $moduleName = strstr($filePath, DS . 'module' . DS);
+ $moduleName = substr($moduleName, 0, strpos($moduleName, DS, 9));
+ $moduleName = basename($moduleName);
+ $extPath = $this->app->getExtensionRoot() . 'custom' . DS . $moduleName . DS . 'ext' . DS;
+ switch($action)
+ {
+ case 'extendModel':
+ $fileName = empty($fileName) ? strtolower(basename($filePath)) . '.php' : $fileName;
+ return $extPath . 'model' . DS . $fileName;
+ case 'extendControl':
+ $fileName = strtolower(basename($filePath)) . '.php';
+ return $extPath . 'control' . DS . $fileName;
+ case 'override':
+ $fileName = basename($filePath);
+ return $extPath . 'view' . DS . $fileName;
+ case 'extendOther':
+ $editName = basename($filePath);
+ $fileName = empty($fileName) ? $editName: $fileName;
+ if($editName == 'config.php') return $extPath . 'config' .DS . $fileName;
+ elseif(strpos($editName, '.php') !== false) return $extPath . 'lang' . DS . str_replace('.php', '', $editName) . DS . $fileName;
+ else return $extPath . substr($editName, strrpos($editName, '.') + 1) . DS . substr($editName, 0, strrpos($editName, '.')) . DS . $fileName;
+ default:
+ if(empty($fileName)) die(js::error($this->lang->editor->emptyFileName));
+ $action = strtolower(str_replace('new', '', $action));
+ if($action == 'hook') return $extPath . 'view' . DS . $fileName;
+ elseif($action == 'method') return $extPath . basename($filePath, '.php') . DS . $fileName;
+ elseif($action == 'extend') return $filePath . DS . $fileName;
+ elseif($action == 'config') return $extPath . 'config' . DS . $fileName;
+ elseif($action == 'js') return $extPath . 'js' . DS . substr($fileName, 0, strrpos($fileName, '.')) . DS . $fileName;
+ elseif($action == 'css') return $extPath . 'css' . DS . substr($fileName, 0, strrpos($fileName, '.')) . DS . $fileName;
+ else return $extPath . 'lang' . DS . str_replace('_', '-', $action) . DS . $fileName;
+ }
+ }
+}
diff --git a/module/editor/view/deny.html.php b/module/editor/view/deny.html.php
new file mode 100644
index 0000000000..8b836b6715
--- /dev/null
+++ b/module/editor/view/deny.html.php
@@ -0,0 +1,23 @@
+
+ * @package editor
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+?>
+getModuleRoot() . 'common/view/header.lite.html.php';?>
+
+
+
lang->editor->onlyLocalVisit;?>
+
+getModuleRoot() . 'common/view/footer.lite.html.php';?>
+
diff --git a/module/editor/view/edit.html.php b/module/editor/view/edit.html.php
new file mode 100644
index 0000000000..ae61933d82
--- /dev/null
+++ b/module/editor/view/edit.html.php
@@ -0,0 +1,141 @@
+
+ * @package editor
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+?>
+getModuleRoot() . 'common/view/header.lite.html.php';?>
+clientLang);
+js::import($jsRoot . 'monaco-editor/min/vs/loader.js');
+?>
+
+
+
+
+ editor->filePath;?>
+
+
+
+
+
+
+
+
+getModuleRoot() . 'common/view/footer.lite.html.php';?>
diff --git a/module/editor/view/extend.html.php b/module/editor/view/extend.html.php
new file mode 100644
index 0000000000..14f5b4d178
--- /dev/null
+++ b/module/editor/view/extend.html.php
@@ -0,0 +1,42 @@
+
+ * @package editor
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+?>
+getModuleRoot() . 'common/view/header.lite.html.php';?>
+
+
editor->modules[$module])? $lang->editor->modules[$module] : $module;?>
+
+
+
+
+
+
+
+
diff --git a/module/editor/view/index.html.php b/module/editor/view/index.html.php
new file mode 100644
index 0000000000..52ad67d1a1
--- /dev/null
+++ b/module/editor/view/index.html.php
@@ -0,0 +1,59 @@
+
+ * @package editor
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+?>
+getModuleRoot() . 'dev/view/header.html.php';?>
+
+
+
+ editor->turnOff;
+ echo html::a($this->createLink('editor', 'switch', 'status=0'), $lang->dev->switchList['0'], 'hiddenwin', "class='btn btn-sm'");
+ ?>
+
+
+
+
+getModuleRoot() . 'common/view/footer.html.php';?>
diff --git a/module/editor/view/newpage.html.php b/module/editor/view/newpage.html.php
new file mode 100644
index 0000000000..413ff8d9f9
--- /dev/null
+++ b/module/editor/view/newpage.html.php
@@ -0,0 +1,37 @@
+
+ * @package editor
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+?>
+getModuleRoot() . 'common/view/header.lite.html.php';?>
+
+
+
+getModuleRoot() . 'common/view/footer.lite.html.php';?>
diff --git a/module/group/lang/resource.php b/module/group/lang/resource.php
index d89215cffd..d8f435e528 100644
--- a/module/group/lang/resource.php
+++ b/module/group/lang/resource.php
@@ -69,13 +69,14 @@ $lang->moduleOrder[195] = 'misc';
$lang->moduleOrder[200] = 'backup';
$lang->moduleOrder[205] = 'cron';
$lang->moduleOrder[210] = 'dev';
-$lang->moduleOrder[215] = 'message';
-$lang->moduleOrder[220] = 'gitlab';
-$lang->moduleOrder[225] = 'mr';
-$lang->moduleOrder[230] = 'app';
-$lang->moduleOrder[235] = 'gogs';
-$lang->moduleOrder[240] = 'gitea';
-$lang->moduleOrder[245] = 'holiday';
+$lang->moduleOrder[215] = 'editor';
+$lang->moduleOrder[220] = 'message';
+$lang->moduleOrder[225] = 'gitlab';
+$lang->moduleOrder[230] = 'mr';
+$lang->moduleOrder[235] = 'app';
+$lang->moduleOrder[240] = 'gogs';
+$lang->moduleOrder[245] = 'gitea';
+$lang->moduleOrder[250] = 'holiday';
$lang->resource = new stdclass();
@@ -1996,13 +1997,26 @@ $lang->cron->methodOrder[35] = 'openProcess';
$lang->resource->dev = new stdclass();
$lang->resource->dev->api = 'api';
$lang->resource->dev->db = 'db';
-$lang->resource->dev->editor = 'editor';
$lang->resource->dev->translate = 'translate';
$lang->dev->methodOrder[5] = 'api';
$lang->dev->methodOrder[10] = 'db';
-$lang->dev->methodOrder[15] = 'editor';
-$lang->dev->methodOrder[20] = 'translate';
+$lang->dev->methodOrder[15] = 'translate';
+
+$lang->resource->editor = new stdclass();
+$lang->resource->editor->index = 'index';
+$lang->resource->editor->extend = 'extend';
+$lang->resource->editor->edit = 'edit';
+$lang->resource->editor->newPage = 'newPage';
+$lang->resource->editor->save = 'save';
+$lang->resource->editor->delete = 'delete';
+
+$lang->editor->methodOrder[5] = 'index';
+$lang->editor->methodOrder[10] = 'extend';
+$lang->editor->methodOrder[15] = 'edit';
+$lang->editor->methodOrder[20] = 'newPage';
+$lang->editor->methodOrder[25] = 'save';
+$lang->editor->methodOrder[30] = 'delete';
global $config;
$inUpgrade = (defined('IN_UPGRADE') and IN_UPGRADE);
diff --git a/module/repo/view/ajaxgeteditorcontent.html.php b/module/repo/view/ajaxgeteditorcontent.html.php
index 96ad4aba5e..30952362b1 100644
--- a/module/repo/view/ajaxgeteditorcontent.html.php
+++ b/module/repo/view/ajaxgeteditorcontent.html.php
@@ -32,7 +32,7 @@ js::set('revision', $revision);
js::set('sourceRevision', $oldRevision);
js::set('encodePath', $this->repo->encodePath($entry));
if($showEditor) js::set('codeContent', $content);
-js::import($jsRoot . '/zui/tabs/tabs.min.js');
+js::import($jsRoot . 'zui/tabs/tabs.min.js');
js::import($jsRoot . 'monaco-editor/min/vs/loader.js');
?>
@@ -338,7 +338,7 @@ $(function()
parent.loadLinkPage(link);
});
- $('#relationTabs').on('click', '.unlinks',function()
+ $('#relationTabs').on('click', '.unlinks', function()
{
var link = $(this).attr('data-link');
$.post(link, function(data)