diff --git a/module/doc/control.php b/module/doc/control.php index ad45009561..a1855adcdf 100644 --- a/module/doc/control.php +++ b/module/doc/control.php @@ -423,6 +423,79 @@ class doc extends control $this->display(); } + /** + * Diff doc. + * + * @param int $docID + * @param int $newVersion + * @param int $oldVersion + * @access public + * @return void + */ + public function diff($docID, $newVersion, $oldVersion = '') + { + if(empty($oldVersion)) $oldVersion = $newVersion - 1; + if(empty($oldVersion) or empty($newVersion)) die(js::error($this->lang->doc->versionNotFound) . js::locate('back')); + + $newDoc = $this->doc->getById($docID, $newVersion, true); + $oldDoc = $this->doc->getById($docID, $oldVersion, true); + if(!$newDoc) die(js::error($this->lang->notFound) . js::locate('back')); + + /* Merge files in doc. */ + $dbOldDocFiles = $this->dao->select('files')->from(TABLE_DOCCONTENT)->where('doc')->eq($docID)->andWhere('version')->eq($oldVersion)->fetch('files'); + $dbOldDocFiles = explode(',', $dbOldDocFiles); + sort($dbOldDocFiles); + $oldDocFiles = array(); + foreach($dbOldDocFiles as $fileID) + { + $file = isset($oldDoc->files[$fileID]) ? $oldDoc->files[$fileID] : ''; + $oldDocFiles[$fileID] = 'File #' . $fileID; + if($file) $oldDocFiles[$fileID] .= ' ' . $file->title . '.' . $file->extension; + } + $oldDoc->files = join("\n", $oldDocFiles); + + $dbNewDocFiles = $this->dao->select('files')->from(TABLE_DOCCONTENT)->where('doc')->eq($docID)->andWhere('version')->eq($newVersion)->fetch('files'); + $dbNewDocFiles = explode(',', $dbNewDocFiles); + sort($dbNewDocFiles); + $newDocFiles = array(); + foreach($dbNewDocFiles as $fileID) + { + $file = isset($newDoc->files[$fileID]) ? $newDoc->files[$fileID] : ''; + $newDocFiles[$fileID] = 'File #' . $fileID; + if($file) $newDocFiles[$fileID] .= ' ' . $file->title . '.' . $file->extension; + } + $newDoc->files = join("\n", $newDocFiles); + + /* Get diff. */ + $diff = new stdclass(); + $diff->content = $this->doc->diff($oldDoc->content, $newDoc->content); + $diff->digest = $this->doc->diff($oldDoc->digest, $newDoc->digest); + $diff->files = $this->doc->diff($oldDoc->files, $newDoc->files); + + /* Check priv when lib is product or project. */ + $lib = $this->doc->getLibByID($newDoc->lib); + $type = $lib->product ? 'product' : ($lib->project ? 'project' : 'custom'); + $this->libs = $this->doc->getLibs($type); + + /* Set menu. */ + $this->doc->setMenu($this->libs, $newDoc->lib, $type); + + $this->view->title = "DOC #$newDoc->id $newDoc->title - " . $this->libs[$newDoc->lib]; + $this->view->position[] = html::a($this->createLink('doc', 'browse', "libID=$newDoc->lib"), $this->libs[$newDoc->lib]); + $this->view->position[] = $this->lang->doc->view; + + $this->view->newDoc = $newDoc; + $this->view->oldDoc = $oldDoc; + $this->view->docID = $docID; + $this->view->lib = $lib; + $this->view->type = $type; + $this->view->newVersion = $newVersion; + $this->view->oldVersion = $oldVersion; + $this->view->diff = $diff; + + $this->display(); + } + /** * Delete a doc. * diff --git a/module/doc/css/diff.css b/module/doc/css/diff.css new file mode 100644 index 0000000000..67d6c0f687 --- /dev/null +++ b/module/doc/css/diff.css @@ -0,0 +1,11 @@ +table.alldiff th{width:70px;border-right:1px solid #ddd;} +table.table.alldiff td:last-child{padding:0px;} +table.table.diff {border:none} +table.alldiff table.table.diff td{border:none;} +table.alldiff table.table.diff .num{border-right:1px solid #ddd; text-align:right; width:20px;} +table.alldiff table.table.diff .oldLine{border-right:1px solid #ddd;} +table.table.diff td.newLine{padding:3px;} +table.table.diff td.oldLine{padding:3px;} +table.table.diff td.line{padding-left:20px;} +table.table.diff td.diffline.newLine{background: #8eff8e} +table.table.diff td.diffline.oldLine{background: #f6b2b2} diff --git a/module/doc/lang/en.php b/module/doc/lang/en.php index 1316eb4e3d..f8c05453b4 100644 --- a/module/doc/lang/en.php +++ b/module/doc/lang/en.php @@ -28,12 +28,17 @@ $lang->doc->addedBy = 'Added by'; $lang->doc->addedDate = 'Added date'; $lang->doc->editedBy = 'Edited by'; $lang->doc->editedDate = 'Edited date'; +$lang->doc->version = 'Version'; $lang->doc->basicInfo = 'Basic Info'; $lang->doc->deleted = 'Deleted'; +$lang->doc->fileObject = 'File object'; +$lang->doc->whiteList = 'White list'; +$lang->doc->contentType = 'Doc type'; $lang->doc->moduleDoc = 'By module'; $lang->doc->searchDoc = 'By search'; -//$lang->doc->allDoc = 'All document'; +$lang->doc->allDoc = 'All doc'; +$lang->doc->openedByMe = 'Added by me'; /* Actions. */ $lang->doc->index = 'Index'; @@ -42,33 +47,60 @@ $lang->doc->edit = 'Edit doc'; $lang->doc->delete = 'Delete doc'; $lang->doc->browse = 'Browse doc'; $lang->doc->view = 'View doc'; +$lang->doc->diff = 'Diff'; $lang->doc->manageType = 'Manage type'; $lang->doc->addType = 'Add type'; $lang->doc->libName = 'Library name'; +$lang->doc->libType = 'Library type'; +$lang->doc->custom = 'Custom library'; $lang->doc->createLib = 'Create library'; $lang->doc->editLib = 'Edit library'; $lang->doc->deleteLib = 'Delete library'; +$lang->doc->fixedMenu = 'Fixed in Menu'; +$lang->doc->removeMenu = 'Move from menu'; /* Browse tabs. */ $lang->doc->allProduct = "All {$lang->productCommon}s"; $lang->doc->allProject = "All {$lang->projectCommon}s"; +$lang->doc->libTypeList['product'] = "{$lang->productCommon} doc"; +$lang->doc->libTypeList['project'] = "{$lang->projectCommon} doc"; +$lang->doc->libTypeList['custom'] = 'Custom library'; + $lang->doc->systemLibs['product'] = "{$lang->productCommon} doc"; $lang->doc->systemLibs['project'] = "{$lang->projectCommon} doc"; -$lang->doc->types['file'] = 'File'; -$lang->doc->types['url'] = 'Link'; -$lang->doc->types['text'] = 'Html'; +$lang->doc->aclList['public'] = 'Public'; +$lang->doc->aclList['custom'] = 'Custom'; +$lang->doc->aclList['private'] = 'Private'; + +$lang->doc->contentTypeList['html'] = 'HTML'; +$lang->doc->contentTypeList['markdown'] = 'MarkDown'; + +$lang->doc->browseType = 'Browse type'; +$lang->doc->browseTypeList['list'] = 'List'; +$lang->doc->browseTypeList['menu'] = 'Menu'; +$lang->doc->browseTypeList['tree'] = 'Tree'; $lang->doc->confirmDelete = "Are you sure to delete this doc?"; $lang->doc->confirmDeleteLib = " Are you sure to delete this doc library?"; $lang->doc->errorEditSystemDoc = "System doc library needn't edit"; $lang->doc->errorEmptyProduct = "{$lang->productCommon} is empty, can not create doc."; $lang->doc->errorEmptyProject = "{$lang->projectCommon} is empty, can not create doc."; +$lang->doc->errorEmptySysLib = "Do not delete all library in system library"; +$lang->doc->accessDenied = "Access to this library denied."; +$lang->doc->noMatched = "No matched library including '%s'"; +$lang->doc->versionNotFount = 'Do not found doc in this version'; $lang->doc->placeholder = new stdclass(); $lang->doc->placeholder->url = 'url'; $lang->doclib = new stdclass(); -$lang->doclib->name = 'Library Name'; +$lang->doclib->name = 'Library Name'; +$lang->doclib->control = 'Control'; +$lang->doclib->acl = 'Power'; +$lang->doclib->group = 'Group'; +$lang->doclib->user = 'User'; +$lang->doclib->files = 'Files'; +$lang->doclib->all = 'All library'; diff --git a/module/doc/lang/zh-cn.php b/module/doc/lang/zh-cn.php index 5f37ec26be..9df288c47e 100644 --- a/module/doc/lang/zh-cn.php +++ b/module/doc/lang/zh-cn.php @@ -47,6 +47,7 @@ $lang->doc->edit = '编辑文档'; $lang->doc->delete = '删除文档'; $lang->doc->browse = '文档列表'; $lang->doc->view = '文档详情'; +$lang->doc->diff = '对比'; $lang->doc->manageType = '维护分类'; $lang->doc->addType = '增加分类'; @@ -90,6 +91,7 @@ $lang->doc->errorEmptyProject = "没有{$lang->projectCommon},无法创建文 $lang->doc->errorEmptySysLib = "系统文档库不能全部删除!"; $lang->doc->accessDenied = "您没有权限访问该文档库!"; $lang->doc->noMatched = '找不到包含"%s"的文档库'; +$lang->doc->versionNotFount = '该版本文档不存在'; $lang->doc->placeholder = new stdclass(); $lang->doc->placeholder->url = '相应的链接地址'; diff --git a/module/doc/model.php b/module/doc/model.php index 8c287f2cbd..0b6cc7f3d6 100644 --- a/module/doc/model.php +++ b/module/doc/model.php @@ -279,6 +279,7 @@ class docModel extends model $this->dao->update(TABLE_DOC)->set('version')->eq($doc->version)->where('id')->eq($doc->id)->exec(); } + $doc->title = isset($docContent->title) ? $docContent->title : ''; $doc->digest = isset($docContent->digest) ? $docContent->digest : ''; $doc->content = isset($docContent->content) ? $docContent->content : ''; $doc->type = isset($docContent->type) ? $docContent->type : ''; @@ -893,4 +894,164 @@ class docModel extends model $node->actions = false; return $node; } + + /** + * Diff doc. + * + * @param string $text1 + * @param string $text2 + * @access public + * @return array + */ + public function diff($text1, $text2) + { + $text1 = explode("\n", trim($text1)); + $text2 = explode("\n", trim($text2)); + $text1Len = count($text1); + $text2Len = count($text2); + $trace = array(); + $trace[-1] = array_fill(-1, $text2Len + 1, +1); + $lcs0 = array_fill(-1, $text2Len + 1, 0); + $lcs1[-1] = 0; + + foreach($text1 as $line) + { + $temp = array(); + $temp[-1] = -1; + for($i = 0; $i < $text2Len; $i++) + { + if($line == $text2[$i]) + { + $lcs1[$i] = $lcs0[$i - 1] + 1; + $temp[$i] = 0; + } + elseif($lcs0[$i] > $lcs1[$i - 1]) + { + $lcs1[$i] = $lcs0[$i]; + $temp[$i] = -1; + } + else + { + $lcs1[$i] = $lcs1[$i - 1]; + $temp[$i] = +1; + } + } + array_push($trace, $temp); + $temp = $lcs1; + $lcs1 = $lcs0; + $lcs0 = $temp; + } + + $same = 0; + $out = false; + $ret = array(); + for($i = $text1Len - 1, $j = $text2Len - 1; $i != -1 || $j != -1;) + { + if($trace[$i][$j] == 0) + { + $same++; + if($out && $same > 0) + { + $outbegina = $i + $same; + $outbeginb = $j + $same; + for($k = $i + $same - 1; $k >= $outbegina; $k--) array_push($ret, array('cmd' => ' ', 'dat' => $text1[$k])); + $dat = array($outbegina, $outenda - $outbegina + 1, $outbeginb, $outendb - $outbeginb + 1); + array_push($ret, array('cmd' => '@', 'dat'=> $dat)); + $out = false; + } + $i--; + $j--; + } + else + { + if(!$out) + { + $same = min($same, 0); + $out = true; + $outenda = $i + $same; + $outendb = $j + $same; + } + for($k = $i + $same; $k > $i; $k--) array_push($ret, array('cmd' => ' ', 'dat' => $text1[$k])); + $same = 0; + if($trace[$i][$j] == -1) + { + array_push($ret, array('cmd' => '-', 'dat' => $text1[$i])); + $i--; + } + else + { + array_push($ret, array('cmd' => '+', 'dat' => $text2[$j])); + $j--; + } + } + } + + if($out) + { + $outbegina = max(0, $same); + $outbeginb = max(0, $same); + for($k = $same - 1; $k >= $outbegina; $k--) array_push($ret, array('cmd' => ' ', 'dat' => $text1[$k])); + $dat = array($outbegina, $outenda - $outbegina + 1, $outbeginb, $outendb - $outbeginb + 1); + array_push($ret, array('cmd' => '@', 'dat'=> $dat)); + } + + $diffs = array_reverse($ret); + if(empty($diffs)) return array(); + + $processedDiff = array(); + foreach($diffs as $i => $diff) + { + if($diff['cmd'] == '@') + { + $max = max($diff['dat'][1], $diff['dat'][3]); + for($number = 0; $number < $max; $number++) + { + $oldLineNO = $diff['dat'][0] + $number; + $newLineNO = $diff['dat'][2] + $number; + $oldLine = ''; + $newLine = ''; + if($diff['dat'][1] > 0 and $diff['dat'][1] >= $number + 1) $oldLine = $diffs[$i + $number + 1]['dat']; + if($diff['dat'][3] > 0 and $diff['dat'][3] >= $number + 1) $newLine = $diffs[$i + $number + 1 + $diff['dat'][1]]['dat']; + $processedDiff['old'][$oldLineNO] = $oldLine; + $processedDiff['new'][$newLineNO] = $newLine; + } + + } + } + return $processedDiff; + } + + /** + * Get line number by diff. + * + * @param array $diff + * @param int $i + * @param int $lineNO + * @access public + * @return array + */ + public function getLineNumber($diff, $i, $lineNO) + { + $action = ''; + if(isset($diff[$i])) + { + if(empty($diff[$i])) + { + $showNumber = ''; + $action = ''; + } + else + { + $showNumber = $lineNO + 1; + $action = 'diff'; + } + } + else + { + $showNumber = $lineNO + 1; + $action = ''; + } + + return array($showNumber, $action, 'number' => $showNumber, 'action' => $action); + } } diff --git a/module/doc/view/diff.html.php b/module/doc/view/diff.html.php new file mode 100644 index 0000000000..c3d145daf3 --- /dev/null +++ b/module/doc/view/diff.html.php @@ -0,0 +1,94 @@ + + * @package doc + * @version $Id: view.html.php 975 2010-07-29 03:30:25Z jajacn@126.com $ + * @link http://www.zentao.net + */ +?> + +
+
+ icons['doc']);?> + title;?> + + deleted):?> + doc->deleted;?> + +
+
+
+
+ + title != $newDoc->title):?> + + + + + + + $diffLines):?> + + + + + + +
doc->title?>title?>title?>
doc->$field?> + + $field))); + $newLines = explode("\n", htmlspecialchars(trim($newDoc->$field))); + $lines = max(count($oldLines), count($newLines)); + $oldLineNO = 0; + $newLineNO = 0; + ?> + + + doc->getLineNumber($diffLines['old'], $i, $oldLineNO); + $showLine = ''; + if($showNumber) + { + $showLine = $oldLines[$oldLineNO]; + $oldLineNO ++; + } + elseif(isset($oldLines[$oldLineNO]) and empty($oldLines[$oldLineNO])) + { + $showNumber = $oldLineNO + 1; + $oldLineNO ++; + if(isset($diffLines['old'][$i])) $action = 'diff'; + } + if($action) $showLine = " " . $showLine; + ?> + + + doc->getLineNumber($diffLines['new'], $i, $newLineNO); + $showLine = ''; + if($showNumber) + { + $showLine = $newLines[$newLineNO]; + $newLineNO ++; + } + elseif(isset($newLines[$newLineNO]) and empty($newLines[$newLineNO])) + { + $showNumber = $newLineNO + 1; + $newLineNO ++; + if(isset($diffLines['new'][$i])) $action = 'diff'; + } + if($action) $showLine = " " . $showLine; + ?> + + + + +
+
+
+ + diff --git a/module/doc/view/view.html.php b/module/doc/view/view.html.php index 4d0cb7b259..f31e2c4e45 100644 --- a/module/doc/view/view.html.php +++ b/module/doc/view/view.html.php @@ -41,6 +41,19 @@ if(!$doc->deleted) { ob_start(); + if($doc->version > 1 and common::hasPriv('doc', 'diff')) + { + echo "
"; + echo ""; + echo ""; + echo "
"; + } echo "
"; common::printIcon('doc', 'edit', $params); common::printIcon('doc', 'delete', $params, '', 'button', '', 'hiddenwin');