Merge branch 'master' of https://gitlab.zcorp.cc/easycorp/zentaopms into sprint/189_package_lanzongjun

This commit is contained in:
lanzongjun
2022-05-25 09:45:32 +08:00
264 changed files with 7748 additions and 4463 deletions
+3 -1
View File
@@ -58,7 +58,9 @@ class productEntry extends Entry
$product->bugStatistic = $this->loadModel('bug')->getStatistic($productID);
break;
case 'moduleoptionmenu':
$product->moduleOptionMenu = $this->loadModel('tree')->getOptionMenu($productID, 'story', 0, '0');
$modules = $this->loadModel('tree')->getOptionMenu($productID, $this->param('moduleType', 'story'));
$product->moduleOptionMenu = array();
foreach($modules as $id => $name) $product->moduleOptionMenu[] = array('id' => $id, 'name' => $name);
break;
case 'parentstories':
$product->parentstories= $this->loadModel('story')->getParentStoryPairs($productID);
+1
View File
@@ -24,6 +24,7 @@ $config->timezone = 'Asia/Shanghai'; // 时区设置。 The tim
$config->webRoot = ''; // URL根目录。 The root path of the url.
$config->customSession = false; // 是否开启自定义session的存储路径。Whether custom the session save path.
$config->edition = 'open'; // 设置系统的edition,可选值:open|biz|max。Set edition, optional: open|biz|max.
$config->tabSession = true; // 是否开启浏览器新标签独立session.
/* 框架路由相关设置。Routing settings. */
$config->requestType = 'PATH_INFO'; // 请求类型:PATH_INFO|PATHINFO2|GET。 The request type: PATH_INFO|PATH_INFO2|GET.
+1
View File
@@ -20,6 +20,7 @@ $filter->default->paramName = 'reg::paramName';
$filter->default->paramValue = 'reg::paramValue';
$filter->default->get['onlybody'] = 'equal::yes';
$filter->default->get['tid'] = 'reg::word';
$filter->default->get['HTTP_X_REQUESTED_WITH'] = 'equal::XMLHttpRequest';
$filter->default->cookie['lang'] = 'reg::lang';
+7
View File
@@ -0,0 +1,7 @@
ALTER TABLE `zt_task` ADD `fromIssue` mediumint(8) unsigned NOT NULL DEFAULT '0' AFTER `fromBug`;
ALTER TABLE `zt_kanbanspace` ADD COLUMN `activatedBy` char(30) NOT NULL AFTER `closedDate`;
ALTER TABLE `zt_kanbanspace` ADD COLUMN `activatedDate` datetime NOT NULL AFTER `activatedBy`;
ALTER TABLE `zt_kanban` ADD COLUMN `activatedBy` char(30) NOT NULL AFTER `closedDate`;
ALTER TABLE `zt_kanban` ADD COLUMN `activatedDate` datetime NOT NULL AFTER `activatedBy`;
Regular → Executable
+5
View File
@@ -760,6 +760,8 @@ CREATE TABLE `zt_kanbanspace` (
`lastEditedDate` datetime NOT NULL,
`closedBy` char(30) NOT NULL,
`closedDate` datetime NOT NULL,
`activatedBy` char(30) NOT NULL,
`activatedDate` datetime NOT NULL,
`deleted` enum('0', '1') NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
@@ -786,6 +788,8 @@ CREATE TABLE `zt_kanban` (
`lastEditedDate` datetime NOT NULL,
`closedBy` char(30) NOT NULL,
`closedDate` datetime NOT NULL,
`activatedBy` char(30) NOT NULL,
`activatedDate` datetime NOT NULL,
`deleted` enum('0', '1') NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
@@ -1457,6 +1461,7 @@ CREATE TABLE IF NOT EXISTS `zt_task` (
`storyVersion` smallint(6) NOT NULL default '1',
`designVersion` smallint(6) unsigned NOT NULL,
`fromBug` mediumint(8) unsigned NOT NULL default '0',
`fromIssue` mediumint(8) unsigned NOT NULL default '0',
`name` varchar(255) NOT NULL,
`type` varchar(20) NOT NULL,
`pri` tinyint(3) unsigned NOT NULL default '0',
+8 -3
View File
@@ -515,10 +515,15 @@ class baseEntry
if(!isset($object->$key)) continue;
$pos = strpos($type, ']');
if($pos !== FALSE)
if($pos !== false)
{
$is_array = true;
$type = substr($type, $pos + 1);
$isArray = true;
$type = substr($type, $pos + 1);
}
else if(strpos($type, 'array') !== false)
{
$isArray = true;
$type = 'object';
}
/* Format value. */
+19 -3
View File
@@ -160,9 +160,12 @@ class baseHelper
public static function processOnlyBodyParam($link, $onlyBody = false)
{
global $config;
if(!$onlyBody and !self::inOnlyBodyMode()) return $link;
$onlybodyString = strpos($link, '?') === false ? "?onlybody=yes" : "&onlybody=yes";
return $link . $onlybodyString;
$sign = strpos($link, '?') === false ? "?" : "&";
$appendString = '';
if($onlyBody or self::inOnlyBodyMode()) $appendString = $sign . "onlybody=yes";
if(self::isWithTID()) $appendString .= empty($appendString) ? "{$sign}tid={$_GET['tid']}" : "&tid={$_GET['tid']}";
return $link . $appendString;
}
/**
@@ -177,6 +180,19 @@ class baseHelper
return (isset($_GET['onlybody']) and $_GET['onlybody'] == 'yes');
}
/**
* Is with tid.
*
* @static
* @access public
* @return bool
*/
public static function isWithTID()
{
global $config;
return (!empty($config->tabSession) and isset($_GET['tid']));
}
/**
* 使用helper::import()来引入文件,不要直接使用include或者require.
* Using helper::import() to import a file, instead of include or require.
+201 -2
View File
@@ -964,6 +964,32 @@ class baseRouter
{
if(defined('SESSION_STARTED')) return;
if(ini_get('session.save_handler') == 'files' and isset($_GET['tid']))
{
$savePath = ini_get('session.save_path');
$writable = is_writable($savePath);
if(!$writable)
{
$savePath = $this->getTmpRoot() . 'session';
if(!is_dir($savePath)) mkdir($savePath, 0777, true);
$writable = is_writable($savePath);
if($writable) session_save_path($this->getTmpRoot() . 'session');
}
if($writable)
{
$ztSessionHandler = new ztSessionHandler($_GET['tid']);
session_set_save_handler(
array($ztSessionHandler, "open"),
array($ztSessionHandler, "close"),
array($ztSessionHandler, "read"),
array($ztSessionHandler, "write"),
array($ztSessionHandler, "destroy"),
array($ztSessionHandler, "gc")
);
}
}
/* If request header has token, use it as session for authentication. */
if(isset($_SERVER['HTTP_TOKEN'])) session_id($_SERVER['HTTP_TOKEN']);
@@ -973,9 +999,13 @@ class baseRouter
if($this->config->customSession) session_save_path($this->getTmpRoot() . 'session');
session_start();
$this->sessionID = session_id();
$this->sessionID = isset($ztSessionHandler) ? $ztSessionHandler->getSessionID() : session_id();
if(isset($_GET[$this->config->sessionVar])) helper::restartSession($_GET[$this->config->sessionVar]);
if(isset($_GET[$this->config->sessionVar]))
{
helper::restartSession($_GET[$this->config->sessionVar]);
$this->sessionID = isset($ztSessionHandler) ? $ztSessionHandler->getSessionID() : session_id();
}
define('SESSION_STARTED', true);
}
@@ -2168,6 +2198,7 @@ class baseRouter
/* Remove these three params. */
unset($passedParams['onlybody']);
unset($passedParams['tid']);
unset($passedParams['HTTP_X_REQUESTED_WITH']);
/* Check params from URL. */
@@ -3038,3 +3069,171 @@ class EndResponseException extends \Exception
return $this->content;
}
}
/**
* ZenTao session handler.
*
* @package framework
*/
class ztSessionHandler
{
public $sessSavePath;
public $tagID;
public $sessionFile;
public $sessionID;
public $rawID;
public $rawFile;
/**
* Construct.
*
* @param string $tagID
* @access public
* @return void
*/
public function __construct($tagID = '')
{
$this->tagID = $tagID;
ini_set('session.save_handler', 'files');
}
/**
* Get sessionID
*
* @access public
* @return string
*/
public function getSessionID()
{
return $this->sessionID;
}
/**
* Get session file.
*
* @param string $id
* @access public
* @return string
*/
public function getSessionFile($id)
{
if(!empty($this->sessionFile)) return $this->sessionFile;
$sessionID = $id;
if($this->tagID) $sessionID = md5($id . $this->tagID);
$fileName = "sess_$sessionID";
$this->sessionFile = $this->sessSavePath . '/' . $fileName;
$this->sessionID = $sessionID;
$this->rawID = $id;
$this->rawFile = $this->sessSavePath . '/' . "sess_$id";
return $this->sessionFile;
}
/**
* Open
*
* @param string $savePath
* @param string $sessionName
* @access public
* @return bool
*/
public function open($savePath, $sessionName)
{
$this->sessSavePath = $savePath;
return true;
}
/**
* Close
*
* @access public
* @return bool
*/
public function close()
{
return true;
}
/**
* Read
*
* @param string $id
* @access public
* @return bool
*/
public function read($id)
{
$sessFile = $this->getSessionFile($id);
if(!file_exists($sessFile))
{
($this->tagID and file_exists($this->rawFile)) ? copy($this->rawFile, $sessFile) : touch($sessFile);
}
return (string) file_get_contents($sessFile);
}
/**
* Write
*
* @param string $id
* @param string $sessData
* @access public
* @return bool
*/
public function write($id, $sessData)
{
$sessFile = $this->getSessionFile($id);
if(file_put_contents($sessFile, $sessData))
{
if(strpos($sessData, 'user|') !== false)
{
if(file_exists($this->rawFile))
{
$rawSessContent = (string) file_get_contents($this->rawFile);
if(strpos($rawSessContent, 'user|') === false) copy($sessFile, $this->rawFile);
}
else
{
copy($sessFile, $this->rawFile);
}
}
return true;
}
return false;
}
/**
* Destroy
*
* @param string $id
* @access public
* @return bool
*/
public function destroy($id)
{
$sessFile = $this->getSessionFile($id);
@unlink($sessFile);
touch($sessFile);
return true;
}
/**
* GC
*
* @param int $maxlifeTime
* @access public
* @return bool
*/
public function gc($maxlifeTime)
{
$time = time();
foreach(glob("$this->sessSavePath/sess_*") as $fileName)
{
if(filemtime($fileName) + $maxlifeTime < $time) @unlink($fileName);
}
return true;
}
}
+6 -4
View File
@@ -584,13 +584,15 @@ class router extends baseRouter
*/
public function getURI($full = false)
{
$URI = !empty($this->rawURI) ? $this->rawURI : $this->URI;
$URI = !empty($this->rawURI) ? $this->rawURI : $this->URI;
$tidParam = ($this->config->requestType == 'PATH_INFO' and helper::isWithTID()) ? "?tid={$_GET['tid']}" : '';
if($full and $this->config->requestType == 'PATH_INFO')
{
if($URI) return $this->config->webRoot . $URI . '.' . $this->viewType;
return $this->config->webRoot;
if($URI) return $this->config->webRoot . $URI . '.' . $this->viewType . $tidParam;
return $this->config->webRoot . $tidParam;
}
return $URI;
return $URI . $tidParam;
}
/**
+3 -3
View File
@@ -661,11 +661,11 @@ class baseValidater
global $config;
if(empty($config->framework->filterXSS)) return $var;
if(stripos($var, '<script') !== false)
if(stripos($var, '&lt;script') !== false or stripos($var, '<script') !== false)
{
$var = (string)$var;
$evils = array('appendchild(', 'createElement(', 'xss.re', 'onfocus', 'onclick', 'innerHTML', 'replaceChild(', 'html(', 'append(', 'appendTo(', 'prepend(', 'prependTo(', 'after(', 'insertBefore', 'before(', 'replaceWith(');
$replaces = array('a p p e n d c h i l d (', 'c r e a t e E l e m e n t (', 'x s s . r e', 'o n f o c u s', 'o n c l i c k', 'i n n e r H T M L', 'r e p l a c e C h i l d (', 'h t m l (', 'a p p e n d (', 'a p p e n d T o (', 'p r e p e n d (', 'p r e p e n d T o (', 'a f t e r (', 'i n s e r t B e f o r e (', 'b e f o r e (', 'r e p l a c e W i t h (');
$evils = array('appendchild(', 'createElement(', 'xss.re', 'onfocus', 'onclick', 'innerHTML', 'replaceChild(', 'html(', 'append(', 'appendTo(', 'prepend(', 'prependTo(', 'after(', 'insertBefore', 'before(', 'replaceWith(', 'alert(', 'confirm(');
$replaces = array('a p p e n d c h i l d (', 'c r e a t e E l e m e n t (', 'x s s . r e', 'o n f o c u s', 'o n c l i c k', 'i n n e r H T M L', 'r e p l a c e C h i l d (', 'h t m l (', 'a p p e n d (', 'a p p e n d T o (', 'p r e p e n d (', 'p r e p e n d T o (', 'a f t e r (', 'i n s e r t B e f o r e (', 'b e f o r e (', 'r e p l a c e W i t h (', 'a l e r t (', 'c o n f i r m (');
$var = str_ireplace($evils, $replaces, $var);
}
+4 -4
View File
@@ -509,10 +509,7 @@ class baseHTML
setcookie('goback', json_encode($gobackList), $config->cookieLife, $config->webRoot, '', $config->cookieSecure, false);
}
$dataApp = array_search($gobackLink, $gobackList) ? array_search($gobackLink, $gobackList) : '';
$dataApp = empty($dataApp) ? '' : "data-app='$dataApp'";
return "<a href='{$gobackLink}' class='btn btn-back $class' $dataApp $misc>{$label}</a>";
return "<a href='{$gobackLink}' class='btn btn-back $class' data-app='$tab' $misc>{$label}</a>";
}
/**
@@ -1168,6 +1165,9 @@ EOT;
$jsConfig->runMode = $runMode;
$jsConfig->timeout = isset($config->timeout) ? $config->timeout : '';
$jsConfig->pingInterval = isset($config->pingInterval) ? $config->pingInterval : '';
$jsConfig->onlybody = zget($_GET, 'onlybody', 'no');
$jsConfig->tabSession = $config->tabSession;
if($config->tabSession and helper::isWithTID()) $jsConfig->tid = zget($_GET, 'tid', '');
$jsLang = new stdclass();
$jsLang->submitting = isset($lang->loading) ? $lang->loading : '';
+1
View File
@@ -113,6 +113,7 @@ class dao extends baseDAO
$app->loadLang('workflowfield');
$app->loadConfig('flow');
$app->loadConfig('workflowfield');
foreach($fields as $field)
{
if(isset($data->{$field->field}))
+10
View File
@@ -0,0 +1,10 @@
<?php
// Use this file if you cannot use class autoloading. It will include all the
// files needed for the Markdown parser.
//
// Take a look at the PSR-0-compatible class autoloading implementation
// in the Readme.php file if you want a simple autoloader setup.
require_once dirname(__FILE__) . '/MarkdownInterface.php';
require_once dirname(__FILE__) . '/Markdown.php';
File diff suppressed because it is too large Load Diff
+11
View File
@@ -0,0 +1,11 @@
<?php
// Use this file if you cannot use class autoloading. It will include all the
// files needed for the MarkdownExtra parser.
//
// Take a look at the PSR-0-compatible class autoloading implementation
// in the Readme.php file if you want a simple autoloader setup.
require_once dirname(__FILE__) . '/MarkdownInterface.php';
require_once dirname(__FILE__) . '/Markdown.php';
require_once dirname(__FILE__) . '/MarkdownExtra.php';
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,9 @@
<?php
// Use this file if you cannot use class autoloading. It will include all the
// files needed for the MarkdownInterface interface.
//
// Take a look at the PSR-0-compatible class autoloading implementation
// in the Readme.php file if you want a simple autoloader setup.
require_once dirname(__FILE__) . '/MarkdownInterface.php';
+38
View File
@@ -0,0 +1,38 @@
<?php
/**
* Markdown - A text-to-HTML conversion tool for web writers
*
* @package php-markdown
* @author Michel Fortin <michel.fortin@michelf.com>
* @copyright 2004-2021 Michel Fortin <https://michelf.com/projects/php-markdown/>
* @copyright (Original Markdown) 2004-2006 John Gruber <https://daringfireball.net/projects/markdown/>
*/
namespace Michelf;
/**
* Markdown Parser Interface
*/
interface MarkdownInterface {
/**
* Initialize the parser and return the result of its transform method.
* This will work fine for derived classes too.
*
* @api
*
* @param string $text
* @return string
*/
public static function defaultTransform($text);
/**
* Main function. Performs some preprocessing on the input text
* and pass it through the document gamut.
*
* @api
*
* @param string $text
* @return string
*/
public function transform($text);
}
+27
View File
@@ -0,0 +1,27 @@
<?php
require_once 'Michelf/MarkdownExtra.inc.php';
use Michelf\Markdown;
use Michelf\MarkdownExtra;
class michelf
{
/**
* Convert markdown to html parser.
*
* @param string $mdCodes
* @static
* @access public
* @return void
*/
public static function parse($mdCodes = '')
{
if(strlen($mdCodes) == 0) return '';
$html = Markdown::defaultTransform($mdCodes);
$parser = new MarkdownExtra;
$parser->fn_id_prefix = "post22-";
$html = $parser->transform($mdCodes);
return "<div class='markdown-print'>$html</div>";
}
}
-20
View File
@@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2013-2018 Emanuil Rusev, erusev.com
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
File diff suppressed because it is too large Load Diff
-686
View File
@@ -1,686 +0,0 @@
<?php
#
#
# Parsedown Extra
# https://github.com/erusev/parsedown-extra
#
# (c) Emanuil Rusev
# http://erusev.com
#
# For the full license information, view the LICENSE file that was distributed
# with this source code.
#
#
class parsedownextra extends parsedown
{
# ~
const version = '0.8.0';
# ~
function __construct()
{
if (version_compare(parent::version, '1.7.1') < 0)
{
throw new Exception('ParsedownExtra requires a later version of Parsedown');
}
$this->BlockTypes[':'] []= 'DefinitionList';
$this->BlockTypes['*'] []= 'Abbreviation';
# identify footnote definitions before reference definitions
array_unshift($this->BlockTypes['['], 'Footnote');
# identify footnote markers before before links
array_unshift($this->InlineTypes['['], 'FootnoteMarker');
}
#
# ~
function text($text)
{
$Elements = $this->textElements($text);
# convert to markup
$markup = $this->elements($Elements);
# trim line breaks
$markup = trim($markup, "\n");
# merge consecutive dl elements
$markup = preg_replace('/<\/dl>\s+<dl>\s+/', '', $markup);
# add footnotes
if (isset($this->DefinitionData['Footnote']))
{
$Element = $this->buildFootnoteElement();
$markup .= "\n" . $this->element($Element);
}
return $markup;
}
#
# Blocks
#
#
# Abbreviation
protected function blockAbbreviation($Line)
{
if (preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches))
{
$this->DefinitionData['Abbreviation'][$matches[1]] = $matches[2];
$Block = array(
'hidden' => true,
);
return $Block;
}
}
#
# Footnote
protected function blockFootnote($Line)
{
if (preg_match('/^\[\^(.+?)\]:[ ]?(.*)$/', $Line['text'], $matches))
{
$Block = array(
'label' => $matches[1],
'text' => $matches[2],
'hidden' => true,
);
return $Block;
}
}
protected function blockFootnoteContinue($Line, $Block)
{
if ($Line['text'][0] === '[' and preg_match('/^\[\^(.+?)\]:/', $Line['text']))
{
return;
}
if (isset($Block['interrupted']))
{
if ($Line['indent'] >= 4)
{
$Block['text'] .= "\n\n" . $Line['text'];
return $Block;
}
}
else
{
$Block['text'] .= "\n" . $Line['text'];
return $Block;
}
}
protected function blockFootnoteComplete($Block)
{
$this->DefinitionData['Footnote'][$Block['label']] = array(
'text' => $Block['text'],
'count' => null,
'number' => null,
);
return $Block;
}
#
# Definition List
protected function blockDefinitionList($Line, $Block)
{
if ( ! isset($Block) or $Block['type'] !== 'Paragraph')
{
return;
}
$Element = array(
'name' => 'dl',
'elements' => array(),
);
$terms = explode("\n", $Block['element']['handler']['argument']);
foreach ($terms as $term)
{
$Element['elements'] []= array(
'name' => 'dt',
'handler' => array(
'function' => 'lineElements',
'argument' => $term,
'destination' => 'elements'
),
);
}
$Block['element'] = $Element;
$Block = $this->addDdElement($Line, $Block);
return $Block;
}
protected function blockDefinitionListContinue($Line, array $Block)
{
if ($Line['text'][0] === ':')
{
$Block = $this->addDdElement($Line, $Block);
return $Block;
}
else
{
if (isset($Block['interrupted']) and $Line['indent'] === 0)
{
return;
}
if (isset($Block['interrupted']))
{
$Block['dd']['handler']['function'] = 'textElements';
$Block['dd']['handler']['argument'] .= "\n\n";
$Block['dd']['handler']['destination'] = 'elements';
unset($Block['interrupted']);
}
$text = substr($Line['body'], min($Line['indent'], 4));
$Block['dd']['handler']['argument'] .= "\n" . $text;
return $Block;
}
}
#
# Header
protected function blockHeader($Line)
{
$Block = parent::blockHeader($Line);
if ($Block !== null && preg_match('/[ #]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE))
{
$attributeString = $matches[1][0];
$Block['element']['attributes'] = $this->parseAttributeData($attributeString);
$Block['element']['handler']['argument'] = substr($Block['element']['handler']['argument'], 0, $matches[0][1]);
}
return $Block;
}
#
# Markup
protected function blockMarkup($Line)
{
if ($this->markupEscaped or $this->safeMode)
{
return;
}
if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
{
$element = strtolower($matches[1]);
if (in_array($element, $this->textLevelElements))
{
return;
}
$Block = array(
'name' => $matches[1],
'depth' => 0,
'element' => array(
'rawHtml' => $Line['text'],
'autobreak' => true,
),
);
$length = strlen($matches[0]);
$remainder = substr($Line['text'], $length);
if (trim($remainder) === '')
{
if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
{
$Block['closed'] = true;
$Block['void'] = true;
}
}
else
{
if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
{
return;
}
if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
{
$Block['closed'] = true;
}
}
return $Block;
}
}
protected function blockMarkupContinue($Line, array $Block)
{
if (isset($Block['closed']))
{
return;
}
if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
{
$Block['depth'] ++;
}
if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
{
if ($Block['depth'] > 0)
{
$Block['depth'] --;
}
else
{
$Block['closed'] = true;
}
}
if (isset($Block['interrupted']))
{
$Block['element']['rawHtml'] .= "\n";
unset($Block['interrupted']);
}
$Block['element']['rawHtml'] .= "\n".$Line['body'];
return $Block;
}
protected function blockMarkupComplete($Block)
{
if ( ! isset($Block['void']))
{
$Block['element']['rawHtml'] = $this->processTag($Block['element']['rawHtml']);
}
return $Block;
}
#
# Setext
protected function blockSetextHeader($Line, array $Block = null)
{
$Block = parent::blockSetextHeader($Line, $Block);
if ($Block !== null && preg_match('/[ ]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE))
{
$attributeString = $matches[1][0];
$Block['element']['attributes'] = $this->parseAttributeData($attributeString);
$Block['element']['handler']['argument'] = substr($Block['element']['handler']['argument'], 0, $matches[0][1]);
}
return $Block;
}
#
# Inline Elements
#
#
# Footnote Marker
protected function inlineFootnoteMarker($Excerpt)
{
if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches))
{
$name = $matches[1];
if ( ! isset($this->DefinitionData['Footnote'][$name]))
{
return;
}
$this->DefinitionData['Footnote'][$name]['count'] ++;
if ( ! isset($this->DefinitionData['Footnote'][$name]['number']))
{
$this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » &
}
$Element = array(
'name' => 'sup',
'attributes' => array('id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name),
'element' => array(
'name' => 'a',
'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'),
'text' => $this->DefinitionData['Footnote'][$name]['number'],
),
);
return array(
'extent' => strlen($matches[0]),
'element' => $Element,
);
}
}
private $footnoteCount = 0;
#
# Link
protected function inlineLink($Excerpt)
{
$Link = parent::inlineLink($Excerpt);
$remainder = $Link !== null ? substr($Excerpt['text'], $Link['extent']) : '';
if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches))
{
$Link['element']['attributes'] += $this->parseAttributeData($matches[1]);
$Link['extent'] += strlen($matches[0]);
}
return $Link;
}
#
# ~
#
private $currentAbreviation;
private $currentMeaning;
protected function insertAbreviation(array $Element)
{
if (isset($Element['text']))
{
$Element['elements'] = self::pregReplaceElements(
'/\b'.preg_quote($this->currentAbreviation, '/').'\b/',
array(
array(
'name' => 'abbr',
'attributes' => array(
'title' => $this->currentMeaning,
),
'text' => $this->currentAbreviation,
)
),
$Element['text']
);
unset($Element['text']);
}
return $Element;
}
protected function inlineText($text)
{
$Inline = parent::inlineText($text);
if (isset($this->DefinitionData['Abbreviation']))
{
foreach ($this->DefinitionData['Abbreviation'] as $abbreviation => $meaning)
{
$this->currentAbreviation = $abbreviation;
$this->currentMeaning = $meaning;
$Inline['element'] = $this->elementApplyRecursiveDepthFirst(
array($this, 'insertAbreviation'),
$Inline['element']
);
}
}
return $Inline;
}
#
# Util Methods
#
protected function addDdElement(array $Line, array $Block)
{
$text = substr($Line['text'], 1);
$text = trim($text);
unset($Block['dd']);
$Block['dd'] = array(
'name' => 'dd',
'handler' => array(
'function' => 'lineElements',
'argument' => $text,
'destination' => 'elements'
),
);
if (isset($Block['interrupted']))
{
$Block['dd']['handler']['function'] = 'textElements';
unset($Block['interrupted']);
}
$Block['element']['elements'] []= & $Block['dd'];
return $Block;
}
protected function buildFootnoteElement()
{
$Element = array(
'name' => 'div',
'attributes' => array('class' => 'footnotes'),
'elements' => array(
array('name' => 'hr'),
array(
'name' => 'ol',
'elements' => array(),
),
),
);
uasort($this->DefinitionData['Footnote'], 'self::sortFootnotes');
foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData)
{
if ( ! isset($DefinitionData['number']))
{
continue;
}
$text = $DefinitionData['text'];
$textElements = parent::textElements($text);
$numbers = range(1, $DefinitionData['count']);
$backLinkElements = array();
foreach ($numbers as $number)
{
$backLinkElements[] = array('text' => ' ');
$backLinkElements[] = array(
'name' => 'a',
'attributes' => array(
'href' => "#fnref$number:$definitionId",
'rev' => 'footnote',
'class' => 'footnote-backref',
),
'rawHtml' => '&#8617;',
'allowRawHtmlInSafeMode' => true,
'autobreak' => false,
);
}
unset($backLinkElements[0]);
$n = count($textElements) -1;
if ($textElements[$n]['name'] === 'p')
{
$backLinkElements = array_merge(
array(
array(
'rawHtml' => '&#160;',
'allowRawHtmlInSafeMode' => true,
),
),
$backLinkElements
);
unset($textElements[$n]['name']);
$textElements[$n] = array(
'name' => 'p',
'elements' => array_merge(
array($textElements[$n]),
$backLinkElements
),
);
}
else
{
$textElements[] = array(
'name' => 'p',
'elements' => $backLinkElements
);
}
$Element['elements'][1]['elements'] []= array(
'name' => 'li',
'attributes' => array('id' => 'fn:'.$definitionId),
'elements' => array_merge(
$textElements
),
);
}
return $Element;
}
# ~
protected function parseAttributeData($attributeString)
{
$Data = array();
$attributes = preg_split('/[ ]+/', $attributeString, - 1, PREG_SPLIT_NO_EMPTY);
foreach ($attributes as $attribute)
{
if ($attribute[0] === '#')
{
$Data['id'] = substr($attribute, 1);
}
else # "."
{
$classes []= substr($attribute, 1);
}
}
if (isset($classes))
{
$Data['class'] = implode(' ', $classes);
}
return $Data;
}
# ~
protected function processTag($elementMarkup) # recursive
{
# http://stackoverflow.com/q/1148928/200145
libxml_use_internal_errors(true);
$DOMDocument = new DOMDocument;
# http://stackoverflow.com/q/11309194/200145
$elementMarkup = mb_convert_encoding($elementMarkup, 'HTML-ENTITIES', 'UTF-8');
# http://stackoverflow.com/q/4879946/200145
$DOMDocument->loadHTML($elementMarkup);
$DOMDocument->removeChild($DOMDocument->doctype);
$DOMDocument->replaceChild($DOMDocument->firstChild->firstChild->firstChild, $DOMDocument->firstChild);
$elementText = '';
if ($DOMDocument->documentElement->getAttribute('markdown') === '1')
{
foreach ($DOMDocument->documentElement->childNodes as $Node)
{
$elementText .= $DOMDocument->saveHTML($Node);
}
$DOMDocument->documentElement->removeAttribute('markdown');
$elementText = "\n".$this->text($elementText)."\n";
}
else
{
foreach ($DOMDocument->documentElement->childNodes as $Node)
{
$nodeMarkup = $DOMDocument->saveHTML($Node);
if ($Node instanceof DOMElement and ! in_array($Node->nodeName, $this->textLevelElements))
{
$elementText .= $this->processTag($nodeMarkup);
}
else
{
$elementText .= $nodeMarkup;
}
}
}
# because we don't want for markup to get encoded
$DOMDocument->documentElement->nodeValue = 'placeholder\x1A';
$markup = $DOMDocument->saveHTML($DOMDocument->documentElement);
$markup = str_replace('placeholder\x1A', $elementText, $markup);
return $markup;
}
# ~
protected function sortFootnotes($A, $B) # callback
{
return $A['number'] - $B['number'];
}
#
# Fields
#
protected $regexAttribute = '(?:[#.][-\w]+[ ]*)';
}
@@ -1,590 +0,0 @@
<?php
#
#
# Parsedown Extra Plugin
# https://github.com/taufik-nurrohman/parsedown-extra-plugin
#
# (c) Emanuil Rusev
# http://erusev.com
#
# (c) Taufik Nurrohman
# https://mecha-cms.com
#
# For the full license information, view the LICENSE file that was distributed
# with this source code.
#
#
require 'parsedown.php';
require 'parsedownextra.php';
class parsedownextraplugin extends parsedownextra {
const version = '1.3.6';
public $abbreviationData = array();
public $blockCodeAttributes = array();
public $blockCodeClassFormat = 'language-%s';
public $blockCodeHtml = null;
public $blockQuoteAttributes = array();
public $blockQuoteText = null;
public $codeAttributes = array();
public $codeAttributesOnParent = false;
public $codeHtml = null;
public $figureAttributes = array();
public $figuresEnabled = false;
public $footnoteAttributes = array();
public $footnoteBackLinkAttributes = array();
public $footnoteBackLinkHtml = null;
public $footnoteBackReferenceAttributes = array();
public $footnoteLinkAttributes = array();
public $footnoteLinkHtml = null;
public $footnoteReferenceAttributes = array();
public $headerAttributes = array();
public $headerText = null;
public $imageAttributes = array();
public $imageAttributesOnParent = false;
public $linkAttributes = array();
public $referenceData = array();
public $tableAttributes = array();
public $tableColumnAttributes = array();
public $voidElementSuffix = ' />';
protected $regexAttribute = '(?:[#.][-\w:\\\]+[ ]*|[-\w:\\\]+(?:=(?:["\'][^\n]*?["\']|[^\s]+)?)?[ ]*)';
# Method aliases for every configuration property
public function __call($key, array $arguments = array()) {
$property = lcfirst(substr($key, 3));
if (strpos($key, 'set') === 0 && property_exists($this, $property)) {
$this->{$property} = $arguments[0];
return $this;
}
throw new Exception('Method ' . $key . ' does not exists.');
}
public function __construct() {
if (version_compare(parent::version, '0.8.0-beta-1') < 0) {
throw new Exception('ParsedownExtraPlugin requires a later version of Parsedown');
}
$this->BlockTypes['!'][] = 'Image';
parent::__construct();
}
protected function blockAbbreviation($Line) {
// Allow empty abbreviations
if (preg_match('/^\*\[(.+?)\]:[ ]*$/', $Line['text'], $matches)) {
$this->DefinitionData['Abbreviation'][$matches[1]] = null;
return array('hidden' => true);
}
return parent::blockAbbreviation($Line);
}
protected function blockCodeComplete($Block) {
$this->doSetAttributes($Block['element']['element'], $this->blockCodeAttributes);
$this->doSetContent($Block['element']['element'], $this->blockCodeHtml, true);
// Put code attributes on parent element
if ($this->codeAttributesOnParent) {
if ($this->codeAttributesOnParent === true) {
// $this->codeAttributesOnParent = array_keys($Block['element']['element']['attributes']);
$this->codeAttributesOnParent = array('class', 'id');
}
foreach ((array) $this->codeAttributesOnParent as $Name) {
if (isset($Block['element']['element']['attributes'][$Name])) {
$Block['element']['attributes'][$Name] = $Block['element']['element']['attributes'][$Name];
unset($Block['element']['element']['attributes'][$Name]);
}
}
}
$Block['element']['element']['rawHtml'] = $Block['element']['element']['text'];
$Block['element']['element']['allowRawHtmlInSafeMode'] = true;
unset($Block['element']['element']['text']);
return $Block;
}
protected function blockFencedCode($Line) {
// Re-enable the multiple class name feature
$Line['text'] = strtr(trim($Line['text']), array(
' ' => "\x1A",
'.' => "\x1A."
));
// Enable custom attribute syntax on code block
$Attributes = array();
if (strpos($Line['text'], '{') !== false && substr($Line['text'], -1) === '}') {
$Parts = explode('{', $Line['text'], 2);
$Attributes = $this->parseAttributeData(strtr(substr($Parts[1], 0, -1), "\x1A", ' '));
$Line['text'] = trim($Parts[0]);
}
if (!$Block = parent::blockFencedCode($Line)) {
return;
}
if ($Attributes) {
$Block['element']['element']['attributes'] = $Attributes;
} else if (isset($Block['element']['element']['attributes']['class'])) {
$Classes = explode("\x1A", strtr($Block['element']['element']['attributes']['class'], ' ', "\x1A"));
// `~~~ php` → `<pre><code class="language-php">`
// `~~~ php html` → `<pre><code class="language-php language-html">`
// `~~~ .php` → `<pre><code class="php">`
// `~~~ .php.html` → `<pre><code class="php html">`
// `~~~ .php html` → `<pre><code class="php language-html">`
// `~~~ {.php #foo}` → `<pre><code id="foo" class="php">`
$Results = [];
foreach ($Classes as $Class) {
if ($Class === "" || $Class === strtr($this->blockCodeClassFormat, array('%s' => ""))) {
continue;
}
if ($Class[0] === '.') {
$Results[] = substr($Class, 1);
} else if (preg_match('/^' . strtr(preg_quote($this->blockCodeClassFormat), array('%s' => '\S+')) . '$/', $Class)) {
$Results[] = $Class; // Do nothing!
} else {
$Results[] = sprintf($this->blockCodeClassFormat, $Class);
}
}
if ($Results = array_unique($Results)) {
$Block['element']['element']['attributes']['class'] = implode(' ', $Results);
} else {
unset($Block['element']['element']['attributes']['class']);
}
}
return $Block;
}
protected function blockFencedCodeComplete($Block) {
return $this->blockCodeComplete($Block);
}
protected function blockHeader($Line) {
if (!$Block = parent::blockHeader($Line)) {
return;
}
$Level = strspn($Line['text'], '#');
$this->doSetAttributes($Block['element'], $this->headerAttributes, array($Level));
$this->doSetContent($Block['element'], $this->headerText, false, 'argument', array($Level));
return $Block;
}
protected function blockImage($Line) {
if (!$this->figuresEnabled) {
return;
}
// Match exactly an image syntax in a paragraph (with optional custom attributes, and optional hard break marker)
if (preg_match('/^\!\[[^\n]*?\](\[[^\n]*?\]|\([^\n]*?\))(\s*\{' . $this->regexAttribute . '+?\})?([ ]{2})?$/', $Line['text'])) {
$Block = array(
'description' => "",
'element' => array(
'name' => 'figure',
'attributes' => array(),
'elements' => array(
$this->inlineImage($Line)
)
)
);
$this->doSetAttributes($Block['element'], $this->figureAttributes);
return $Block;
}
return;
}
protected function blockImageComplete($Block) {
if (!empty($Block['description'])) {
$Description = $Block['description'];
$Block['element']['elements'][] = array(
'name' => 'figcaption',
'rawHtml' => $this->{strpos($Description, "\n\n") === false ? 'line' : 'text'}(trim($Description, "\n"))
);
// unset($Block['description']);
}
if ($this->imageAttributesOnParent) {
$Inline = $Block['element']['elements'][0];
if ($this->imageAttributesOnParent === true) {
$this->imageAttributesOnParent = array_keys($Inline['element']['attributes']);
}
foreach ((array) $this->imageAttributesOnParent as $Name) {
if (isset($Inline['element']['attributes'][$Name])) {
// Merge class names
if (
$Name === 'class' &&
isset($Block['element']['attributes'][$Name]) &&
isset($Inline['element']['attributes'][$Name])
) {
$Classes = array_merge(
explode(' ', $Block['element']['attributes'][$Name]),
explode(' ', $Inline['element']['attributes'][$Name])
);
sort($Classes);
$Block['element']['attributes']['class'] = implode(' ', array_unique(array_filter($Classes)));
unset($Block['element']['elements'][0]['element']['attributes'][$Name]);
continue;
}
$Block['element']['attributes'][$Name] = $Inline['element']['attributes'][$Name];
unset($Block['element']['elements'][0]['element']['attributes'][$Name]);
}
}
}
return $Block;
}
protected function blockImageContinue($Line, array $Block) {
if (isset($Block['complete'])) {
return;
}
if (isset($Block['interrupted'])) {
$Block['description'] .= "\n";
unset($Block['interrupted']);
}
if ($Line['indent'] === 0) {
$Block['complete'] = true;
return;
}
if ($Line['indent'] > 0 && $Line['indent'] < 4) {
$Block['description'] .= "\n" . $Line['text'];
return $Block;
}
return;
}
protected function blockQuoteComplete($Block) {
$this->doSetAttributes($Block['element'], $this->blockQuoteAttributes);
$this->doSetContent($Block['element'], $this->blockQuoteText, false, 'arguments');
return $Block;
}
protected function blockSetextHeader($Line, array $Block = null) {
if (!$Block = parent::blockSetextHeader($Line, $Block)) {
return;
}
$Level = $Line['text'][0] === '=' ? 1 : 2;
$this->doSetAttributes($Block['element'], $this->headerAttributes, array($Level));
$this->doSetContent($Block['element'], $this->headerText, false, 'argument', array($Level));
return $Block;
}
protected function blockTableContinue($Line, array $Block) {
if (!$Block = parent::blockTableContinue($Line, $Block)) {
return;
}
$Aligns = $Block['alignments'];
// `<thead>` or `<tbody>`
foreach ($Block['element']['elements'] as $Index0 => &$Element0) {
// `<tr>`
foreach ($Element0['elements'] as $Index1 => &$Element1) {
// `<th>` or `<td>`
foreach ($Element1['elements'] as $Index2 => &$Element2) {
$this->doSetAttributes($Element2, $this->tableColumnAttributes, array($Aligns[$Index2], $Index2, $Index1));
}
}
}
return $Block;
}
protected function blockTableComplete($Block) {
$this->doSetAttributes($Block['element'], $this->tableAttributes);
return $Block;
}
protected function buildFootnoteElement() {
$DefinitionData = $this->DefinitionData['Footnote'];
if (!$Footnotes = parent::buildFootnoteElement()) {
return;
}
$DefinitionKey = array_keys($DefinitionData);
$DefinitionData = array_values($DefinitionData);
$this->doSetAttributes($Footnotes, $this->footnoteAttributes);
foreach ($Footnotes['elements'][1]['elements'] as $Index0 => &$Element0) {
$Name = $DefinitionKey[$Index0];
$Count = $DefinitionData[$Index0]['count'];
$Args = array(is_numeric($Name) ? (float) $Name : $Name, $Count);
$this->doSetAttributes($Element0, $this->footnoteBackReferenceAttributes, $Args);
foreach ($Element0['elements'] as $Index1 => &$Element1) {
if (!isset($Element1['elements'])) {
continue;
}
$Count = 0;
foreach ($Element1['elements'] as $Index2 => &$Element2) {
if (!isset($Element2['name']) || $Element2['name'] !== 'a') {
continue;
}
$Args[1] = ++$Count;
$this->doSetAttributes($Element2, $this->footnoteBackLinkAttributes, $Args);
$this->doSetContent($Element2, $this->footnoteBackLinkHtml, false, 'rawHtml');
}
}
}
return $Footnotes;
}
protected function doGetAttributes($Element) {
if (isset($Element['attributes'])) {
return (array) $Element['attributes'];
}
return array();
}
protected function doGetContent($Element) {
if (isset($Element['text'])) {
return $Element['text'];
}
if (isset($Element['rawHtml'])) {
return $Element['rawHtml'];
}
if (isset($Element['handler']['argument'])) {
return implode("\n", (array) $Element['handler']['argument']);
}
return null;
}
private function doSetLink($Excerpt, $Function) {
if (!$Inline = call_user_func('parent::' . $Function, $Excerpt)) {
return;
}
$this->doSetAttributes($Inline['element'], $this->linkAttributes, array($this->isLocal($Inline['element'], 'href')));
$this->doSetData($this->DefinitionData['Reference'], $this->referenceData);
return $Inline;
}
protected function doSetAttributes(&$Element, $From, $Args = array()) {
$Attributes = $this->doGetAttributes($Element);
$Content = $this->doGetContent($Element);
if (is_callable($From)) {
$Args = array_merge(array($Content, $Attributes, &$Element), $Args);
$Element['attributes'] = array_replace($Attributes, (array) call_user_func_array($From, $Args));
} else {
$Element['attributes'] = array_replace($Attributes, (array) $From);
}
}
protected function doSetContent(&$Element, $From, $Esc = false, $Mode = 'text', $Args = array()) {
$Attributes = $this->doGetAttributes($Element);
$Content = $this->doGetContent($Element);
if ($Esc) {
$Content = parent::escape($Content, true);
}
if (is_callable($From)) {
$Args = array_merge(array($Content, $Attributes, &$Element), $Args);
$Content = call_user_func_array($From, $Args);
} else if (!empty($From)) {
$Content = sprintf($From, $Content);
}
if ($Mode === 'arguments') {
$Element['handler']['argument'] = explode("\n", $Content);
} else if ($Mode === 'argument') {
$Element['handler']['argument'] = $Content;
} else {
$Element[$Mode] = $Content;
}
}
protected function doSetData(&$To, $From) {
$To = array_replace((array) $To, (array) $From);
}
protected function element(array $Element) {
if (!$Any = parent::element($Element)) {
return;
}
if (substr($Any, -3) === ' />') {
if (is_callable($this->voidElementSuffix)) {
$Attributes = $this->doGetAttributes($Element);
$Content = $this->doGetContent($Element);
$Suffix = call_user_func_array($this->voidElementSuffix, [$Content, $Attributes, &$Element]);
} else {
$Suffix = $this->voidElementSuffix;
}
$Any = substr_replace($Any, $Suffix, -3);
}
return $Any;
}
protected function inlineCode($Excerpt) {
if (!$Inline = parent::inlineCode($Excerpt)) {
return;
}
$this->doSetAttributes($Inline['element'], $this->codeAttributes);
$this->doSetContent($Inline['element'], $this->codeHtml, true);
$Inline['element']['rawHtml'] = $Inline['element']['text'];
$Inline['element']['allowRawHtmlInSafeMode'] = true;
unset($Inline['element']['text']);
return $Inline;
}
protected function inlineFootnoteMarker($Excerpt) {
if (!$Inline = parent::inlineFootnoteMarker($Excerpt)) {
return;
}
$Name = null;
if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches)) {
$Name = $matches[1];
}
$Args = array(is_numeric($Name) ? (float) $Name : $Name, $this->DefinitionData['Footnote'][$Name]['count']);
$this->doSetAttributes($Inline['element'], $this->footnoteReferenceAttributes, $Args);
$this->doSetAttributes($Inline['element']['element'], $this->footnoteLinkAttributes, $Args);
$this->doSetContent($Inline['element']['element'], $this->footnoteLinkHtml, false, 'text', $Args);
$Inline['element']['element']['rawHtml'] = $Inline['element']['element']['text'];
$Inline['element']['element']['allowRawHtmlInSafeMode'] = true;
unset($Inline['element']['element']['text']);
return $Inline;
}
protected function inlineImage($Excerpt) {
if (!$Inline = parent::inlineImage($Excerpt)) {
return;
}
$this->doSetAttributes($Inline['element'], $this->imageAttributes, array($this->isLocal($Inline['element'], 'src')));
return $Inline;
}
protected function inlineLink($Excerpt) {
return $this->doSetLink($Excerpt, __FUNCTION__);
}
protected function inlineText($Text) {
$this->doSetData($this->DefinitionData['Abbreviation'], $this->abbreviationData);
return parent::inlineText($Text);
}
protected function inlineUrl($Excerpt) {
return $this->doSetLink($Excerpt, __FUNCTION__);
}
protected function inlineUrlTag($Excerpt) {
return $this->doSetLink($Excerpt, __FUNCTION__);
}
protected function isLocal($Element, $Key) {
$Link = isset($Element['attributes'][$Key]) ? (string) $Element['attributes'][$Key] : null;
if (
// `<a href="">`
$Link === "" ||
// `<a href="../foo/bar">`
// `<a href="/foo/bar">`
// `<a href="?foo=bar">`
// `<a href="&foo=bar">`
// `<a href="#foo">`
strpos('./?&#', $Link[0]) !== false && strpos($Link, '//') !== 0 ||
// `<a href="data:text/html,asdf">`
strpos($Link, 'data:') === 0 ||
// `<a href="javascript:;">`
strpos($Link, 'javascript:') === 0 ||
// `<a href="mailto:as@df">`
strpos($Link, 'mailto:') === 0
) {
return true;
}
if (isset($_SERVER['HTTP_HOST'])) {
$Host = $_SERVER['HTTP_HOST'];
} else if (isset($_SERVER['SERVER_NAME'])) {
$Host = $_SERVER['SERVER_NAME'];
} else {
$Host = "";
}
// `<a href="//example.com">`
if (strpos($Link, '//') === 0 && strpos($Link, '//' . $Host) !== 0) {
return false;
}
if (
// `<a href="https://127.0.0.1">`
strpos($Link, 'https://' . $Host) === 0 ||
// `<a href="http://127.0.0.1">`
strpos($Link, 'http://' . $Host) === 0
) {
return true;
}
// `<a href="foo/bar">`
return strpos($Link, '://') === false;
}
protected function parseAttributeData($attributeString) {
// Allow compact attributes
$attributeString = strtr($attributeString, array(
'#' => ' #',
'.' => ' .'
));
if (strpos($attributeString, '="') !== false || strpos($attributeString, "='") !== false) {
$attributeString = preg_replace_callback('#([-\w]+=)(["\'])([^\n]*?)\2#', function($matches) {
$value = strtr($matches[3], array(
' #' => '#',
' .' => '.',
' ' => "\x1A"
));
return $matches[1] . $matches[2] . $value . $matches[2];
}, $attributeString);
}
$Attributes = array();
foreach (explode(' ', $attributeString) as $v) {
if (!$v) {
continue;
}
// `{#foo}`
if ($v[0] === '#' && isset($v[1])) {
$Attributes['id'] = substr($v, 1);
// `{.foo}`
} else if ($v[0] === '.' && isset($v[1])) {
$Attributes['class'][] = substr($v, 1);
// ~
} else if (strpos($v, '=') !== false) {
$vv = explode('=', $v, 2);
// `{foo=}`
if ($vv[1] === "") {
if ($vv[0] === 'class') {
continue;
}
$Attributes[$vv[0]] = "";
// `{foo="bar baz"}`
// `{foo='bar baz'}`
} else if ($vv[1][0] === '"' && substr($vv[1], -1) === '"' || $vv[1][0] === "'" && substr($vv[1], -1) === "'") {
$values = stripslashes(strtr(substr(substr($vv[1], 1), 0, -1), "\x1A", ' '));
if ($vv[0] === 'class' && isset($Attributes[$vv[0]])) {
$values = explode(' ', $values);
$Attributes[$vv[0]] = array_merge($Attributes[$vv[0]], $values);
} else {
$Attributes[$vv[0]] = $values;
}
// `{foo=bar}`
} else {
if ($vv[0] === 'class' && isset($Attributes[$vv[0]])) {
$Attributes[$vv[0]] = array_merge($Attributes[$vv[0]], [$vv[1]]);
} else {
$Attributes[$vv[0]] = $vv[1];
}
}
// `{foo}`
} else {
if ($v === 'class' && isset($Attributes[$v])) {
continue;
}
$Attributes[$v] = $v;
}
}
if (isset($Attributes['class'])) {
$Attributes['class'] = implode(' ', array_unique((array) $Attributes['class']));
}
return $Attributes;
}
}
+2 -1
View File
@@ -175,7 +175,8 @@ class action extends control
{
$story = $this->loadModel('story')->getById($objectID);
$executions = explode(',', $this->app->user->view->sprints);
if(!array_intersect(array_keys($story->executions), $executions)) return print(js::error($this->lang->error->accessDenied));
$products = explode(',', $this->app->user->view->products);
if(!array_intersect(array_keys($story->executions), $executions) and !in_array($story->product, $products)) return print(js::error($this->lang->error->accessDenied));
}
$actionID = $this->action->create($objectType, $objectID, 'Commented', $this->post->comment);
+21 -7
View File
@@ -162,7 +162,7 @@ $lang->action->desc->hidden = '$date, hidden by <strong>$actor</st
$lang->action->desc->commented = '$date, added by <strong>$actor</strong>.' . "\n";
$lang->action->desc->activated = '$date, activated by <strong>$actor</strong> .' . "\n";
$lang->action->desc->blocked = '$date, blocked by <strong>$actor</strong> .' . "\n";
$lang->action->desc->moved = '$date, moved by <strong>$actor</strong> , which was "$extra".' . "\n";
$lang->action->desc->moved = '$date, moved by <strong>$actor</strong> .' . "\n";
$lang->action->desc->confirmed = '$date, <strong>$actor</strong> confirmed the story change. The latest build is <strong>#$extra</strong>.' . "\n";
$lang->action->desc->caseconfirmed = '$date, <strong>$actor</strong> confirmed the case change. The latest build is <strong>#$extra</strong>' . "\n";
$lang->action->desc->bugconfirmed = '$date, <strong>$actor</strong> confirmed Bug.' . "\n";
@@ -224,6 +224,12 @@ $lang->action->desc->deletechildrenstory = '$date, <strong>$actor</strong> delet
$lang->action->desc->linkrelatedcase = '$date, <strong>$actor</strong> linked a case <strong>$extra</strong>.' . "\n";
$lang->action->desc->unlinkrelatedcase = '$date, <strong>$actor</strong> unlinked a case <strong>$extra</strong>.' . "\n";
/* Used to describe the history of operations link story and bug to productplan. */
$lang->action->desc->linkstory = '$date, 由 <strong>$actor</strong> 关联需求 <strong>$extra</strong> 到计划。' . "\n";
$lang->action->desc->linkbug = '$date, 由 <strong>$actor</strong> 关联BUG <strong>$extra</strong> 到计划。' . "\n";
$lang->action->desc->unlinkstory = '$date, 由 <strong>$actor</strong> 从计划移除需求 <strong>$extra</strong>。' . "\n";
$lang->action->desc->unlinkbug = '$date, 由 <strong>$actor</strong> 从计划移除BUG <strong>$extra</strong>。' . "\n";
/* Used to display dynamic information. */
$lang->action->label = new stdclass();
$lang->action->label->created = 'created ';
@@ -352,6 +358,10 @@ $lang->action->label->importedbuild = 'imported';
$lang->action->label->fromsonarqube = 'created a bug from SonarQube Issue named:';
$lang->action->label->bind = 'bound';
$lang->action->label->unbind = 'unbound';
$lang->action->label->linkstory = 'link stories to';
$lang->action->label->linkbug = 'link bugs to';
$lang->action->label->unlinkstory = 'unlink stories from';
$lang->action->label->unlindbug = 'unlink bugs from';
/* Dynamic information is grouped by object. */
$lang->action->dynamicAction = new stdclass;
@@ -392,12 +402,16 @@ $lang->action->dynamicAction->branch['activated'] = 'Activate Branch';
$lang->action->dynamicAction->branch['setdefaultbranch'] = 'Set Default Branch';
$lang->action->dynamicAction->branch['mergebranch'] = 'Merge Branch';
$lang->action->dynamicAction->productplan['opened'] = 'Create Plan';
$lang->action->dynamicAction->productplan['edited'] = 'Edit Plan';
$lang->action->dynamicAction->productplan['started'] = "Start Plan";
$lang->action->dynamicAction->productplan['finished'] = "Finish Plan";
$lang->action->dynamicAction->productplan['closed'] = "Close Plan";
$lang->action->dynamicAction->productplan['activated'] = "Activate Plan";
$lang->action->dynamicAction->productplan['opened'] = 'Create Plan';
$lang->action->dynamicAction->productplan['edited'] = 'Edit Plan';
$lang->action->dynamicAction->productplan['started'] = "Start Plan";
$lang->action->dynamicAction->productplan['finished'] = "Finish Plan";
$lang->action->dynamicAction->productplan['closed'] = "Close Plan";
$lang->action->dynamicAction->productplan['activated'] = "Activate Plan";
$lang->action->dynamicAction->productplan['linkstory'] = "Link Story";
$lang->action->dynamicAction->productplan['unlinkstory'] = "Unlink Story";
$lang->action->dynamicAction->productplan['linkbug'] = "Link Bug";
$lang->action->dynamicAction->productplan['unlinkbug'] = "Unlink Bug";
$lang->action->dynamicAction->release['opened'] = 'Create Release';
$lang->action->dynamicAction->release['edited'] = 'Edit Release';
+22 -7
View File
@@ -162,7 +162,7 @@ $lang->action->desc->hidden = '$date, 由 <strong>$actor</strong>
$lang->action->desc->commented = '$date, 由 <strong>$actor</strong> 添加备注。' . "\n";
$lang->action->desc->activated = '$date, 由 <strong>$actor</strong> 激活。' . "\n";
$lang->action->desc->blocked = '$date, 由 <strong>$actor</strong> 阻塞。' . "\n";
$lang->action->desc->moved = '$date, 由 <strong>$actor</strong> 移动,之前为 "$extra"。' . "\n";
$lang->action->desc->moved = '$date, 由 <strong>$actor</strong> 移动。' . "\n";
$lang->action->desc->confirmed = '$date, 由 <strong>$actor</strong> 确认' . $lang->SRCommon . '变动,最新版本为<strong>#$extra</strong>。' . "\n";
$lang->action->desc->caseconfirmed = '$date, 由 <strong>$actor</strong> 确认用例变动,最新版本为<strong>#$extra</strong>。' . "\n";
$lang->action->desc->bugconfirmed = '$date, 由 <strong>$actor</strong> 确认Bug。' . "\n";
@@ -224,6 +224,12 @@ $lang->action->desc->deletechildrenstory = '$date, 由 <strong>$actor</strong>
$lang->action->desc->linkrelatedcase = '$date, 由 <strong>$actor</strong> 关联相关用例 <strong>$extra</strong>。' . "\n";
$lang->action->desc->unlinkrelatedcase = '$date, 由 <strong>$actor</strong> 移除相关用例 <strong>$extra</strong>。' . "\n";
/* 用来描述计划关联和移除需求、bug时的历史操作记录。*/
$lang->action->desc->linkstory = '$date, 由 <strong>$actor</strong> 关联需求 <strong>$extra</strong> 到计划。' . "\n";
$lang->action->desc->linkbug = '$date, 由 <strong>$actor</strong> 关联BUG <strong>$extra</strong> 到计划。' . "\n";
$lang->action->desc->unlinkstory = '$date, 由 <strong>$actor</strong> 从计划移除需求 <strong>$extra</strong>。' . "\n";
$lang->action->desc->unlinkbug = '$date, 由 <strong>$actor</strong> 从计划移除BUG <strong>$extra</strong>。' . "\n";
/* 用来显示动态信息。*/
$lang->action->label = new stdclass();
$lang->action->label->created = '创建';
@@ -352,6 +358,10 @@ $lang->action->label->importedbuild = '导入了';
$lang->action->label->fromsonarqube = '由SonarQube问题创建';
$lang->action->label->bind = '绑定了';
$lang->action->label->unbind = '取消绑定了';
$lang->action->label->linkstory = '关联需求到';
$lang->action->label->linkbug = '关联BUG到';
$lang->action->label->unlinkstory = '移除需求从';
$lang->action->label->unlinkbug = '移除BUG从';
/* 动态信息按照对象分组 */
$lang->action->dynamicAction = new stdclass();
@@ -392,12 +402,16 @@ $lang->action->dynamicAction->branch['activated'] = '激活分支';
$lang->action->dynamicAction->branch['setdefaultbranch'] = '设置默认分支';
$lang->action->dynamicAction->branch['mergebranch'] = '合并分支';
$lang->action->dynamicAction->productplan['opened'] = "创建计划";
$lang->action->dynamicAction->productplan['edited'] = "编辑计划";
$lang->action->dynamicAction->productplan['started'] = "开始计划";
$lang->action->dynamicAction->productplan['finished'] = "完成计划";
$lang->action->dynamicAction->productplan['closed'] = "关闭计划";
$lang->action->dynamicAction->productplan['activated'] = "激活计划";
$lang->action->dynamicAction->productplan['opened'] = "创建计划";
$lang->action->dynamicAction->productplan['edited'] = "编辑计划";
$lang->action->dynamicAction->productplan['started'] = "开始计划";
$lang->action->dynamicAction->productplan['finished'] = "完成计划";
$lang->action->dynamicAction->productplan['closed'] = "关闭计划";
$lang->action->dynamicAction->productplan['activated'] = "激活计划";
$lang->action->dynamicAction->productplan['linkstory'] = "关联需求";
$lang->action->dynamicAction->productplan['unlinkstory'] = "移除需求";
$lang->action->dynamicAction->productplan['linkbug'] = "关联BUG";
$lang->action->dynamicAction->productplan['unlinkbug'] = "移除BUG";
$lang->action->dynamicAction->release['opened'] = '创建发布';
$lang->action->dynamicAction->release['edited'] = '编辑发布';
@@ -624,6 +638,7 @@ else
{
$lang->action->label->execution = "$lang->executionCommon|execution|task|executionID=%s";
}
$lang->action->label->task = '任务|task|view|taskID=%s';
$lang->action->label->build = '版本|build|view|buildID=%s';
$lang->action->label->bug = 'Bug|bug|view|bugID=%s';
+13 -1
View File
@@ -73,7 +73,7 @@ class actionModel extends model
if($this->post->uid) $this->file->updateObjectID($this->post->uid, $objectID, $objectType);
/* Call the message notification function. */
$this->loadModel('message')->send($objectType, $objectID, $actionType, $actionID, $actor);
$this->loadModel('message')->send(strtolower($objectType), $objectID, $actionType, $actionID, $actor);
/* Add index for global search. */
$this->saveIndex($objectType, $objectID, $actionType);
@@ -555,6 +555,18 @@ class actionModel extends model
if($history->field == 'git') $history->diff = str_replace('+', '%2B', $history->diff);
}
}
elseif($actionName == 'linkstory' or $actionName == 'unlinkstory')
{
$extra = '';
foreach(explode(',', $action->extra) as $id) $extra .= common::hasPriv('story', 'view') ? html::a(helper::createLink('story', 'view', "storyID=$id"), "#$id ") . ', ' : "#$id, ";
$action->extra = trim(trim($extra), ',');
}
elseif($actionName == 'linkbug' or $actionName == 'unlinkbug')
{
$extra = '';
foreach(explode(',', $action->extra) as $id) $extra .= common::hasPriv('bug', 'view') ? html::a(helper::createLink('bug', 'view', "bugID=$id"), "#$id ") . ', ' : "#$id, ";
$action->extra = trim(trim($extra), ',');
}
$action->comment = $this->file->setImgSize($action->comment, $this->config->action->commonImgSize);
+14 -8
View File
@@ -375,9 +375,12 @@ class apiModel extends model
public function getApiListByRelease($release, $where = '1 = 1 ')
{
$strJoin = array();
foreach($release->snap['apis'] as $api)
if(isset($release->snap['apis']))
{
$strJoin[] = "(spec.doc = {$api['id']} and spec.version = {$api['version']} )";
foreach($release->snap['apis'] as $api)
{
$strJoin[] = "(spec.doc = {$api['id']} and spec.version = {$api['version']} )";
}
}
if($strJoin) $where .= 'and (' . implode(' or ', $strJoin) . ')';
@@ -404,7 +407,7 @@ class apiModel extends model
$rel = $this->getRelease(0, 'byId', $release);
$where = "1=1 and lib = $libID ";
if($moduleID > 0)
if($moduleID > 0 and isset($rel->snap['modules']))
{
$sub = array();
foreach($rel->snap['modules'] as $module)
@@ -497,9 +500,12 @@ class apiModel extends model
public function getStructListByRelease($release, $where = '1 = 1 ', $orderBy = 'id')
{
$strJoin = array();
foreach($release->snap['structs'] as $struct)
if(isset($release->snap['structs']))
{
$strJoin[] = "(object.id = {$struct['id']} and spec.version = {$struct['version']} )";
foreach($release->snap['structs'] as $struct)
{
$strJoin[] = "(object.id = {$struct['id']} and spec.version = {$struct['version']} )";
}
}
if($strJoin) $where .= 'and (' . implode(' or ', $strJoin) . ')';
@@ -619,8 +625,8 @@ class apiModel extends model
foreach($_POST as $key => $value) $param .= ',' . $key . '=' . $value;
$param = ltrim($param, ',');
}
$url = rtrim($host, '/') . inlink('getModel', "moduleName=$moduleName&methodName=$methodName&params=$param", 'json');
$url .= $this->config->requestType == "PATH_INFO" ? '?' : '&';
$url = rtrim($host, '/') . inlink('getModel', "moduleName=$moduleName&methodName=$methodName&params=$param", 'json');
$url .= strpos($url, '?') === false ? '?' : '&';
$url .= $this->config->sessionVar . '=' . session_id();
}
else
@@ -631,7 +637,7 @@ class apiModel extends model
$param = ltrim($param, '&');
}
$url = rtrim($host, '/') . helper::createLink($moduleName, $methodName, $param, 'json');
$url .= $this->config->requestType == "PATH_INFO" ? '?' : '&';
$url .= strpos($url, '?') === false ? '?' : '&';
$url .= $this->config->sessionVar . '=' . session_id();
}
+17 -1
View File
@@ -326,7 +326,7 @@ $config->bug->datatable->fieldList['closedDate']['required'] = 'no';
$config->bug->datatable->fieldList['lastEditedBy']['title'] = 'lastEditedBy';
$config->bug->datatable->fieldList['lastEditedBy']['fixed'] = 'no';
$config->bug->datatable->fieldList['lastEditedBy']['width'] = '80';
$config->bug->datatable->fieldList['lastEditedBy']['width'] = '90';
$config->bug->datatable->fieldList['lastEditedBy']['required'] = 'no';
$config->bug->datatable->fieldList['lastEditedDate']['title'] = 'lastEditedDateAB';
@@ -338,3 +338,19 @@ $config->bug->datatable->fieldList['actions']['title'] = 'actions';
$config->bug->datatable->fieldList['actions']['fixed'] = 'right';
$config->bug->datatable->fieldList['actions']['width'] = '150';
$config->bug->datatable->fieldList['actions']['required'] = 'yes';
$config->bug->colorList = new stdclass();
$config->bug->colorList->pri[1] = '#d50000';
$config->bug->colorList->pri[2] = '#ff9800';
$config->bug->colorList->pri[3] = '#2098ee';
$config->bug->colorList->pri[4] = '#009688';
$config->bug->colorList->pri[5] = '#919090';
$config->bug->colorList->pri[6] = '#B6B4B4';
$config->bug->colorList->pri[7] = '#BDBEBD';
$config->bug->colorList->severity[1] = '#c62828';
$config->bug->colorList->severity[2] = '#ff8f00';
$config->bug->colorList->severity[3] = '#fdd835';
$config->bug->colorList->severity[4] = '#cddc39';
$config->bug->colorList->severity[5] = '#8bc34a';
$config->bug->colorList->severity[6] = '#B6B4B4';
$config->bug->colorList->severity[7] = '#BDBEBD';
+3 -3
View File
@@ -126,7 +126,7 @@ class bug extends control
$queryID = ($browseType == 'bysearch') ? (int)$param : 0;
/* Set session. */
$this->session->set('bugList', $this->app->getURI(true), 'qa');
$this->session->set('bugList', $this->app->getURI(true) . "#app={$this->app->tab}", 'qa');
/* Set moduleTree. */
if($browseType == '')
@@ -267,7 +267,7 @@ class bug extends control
public function report($productID, $browseType, $branchID, $moduleID, $chartType = 'default')
{
$this->loadModel('report');
$this->view->charts = array();
$this->view->charts = array();
if(!empty($_POST))
{
@@ -1447,7 +1447,7 @@ class bug extends control
$this->loadModel('score')->create('ajax', 'batchOther');
}
if($type == 'product' || $type == 'my') return print(js::locate($this->session->bugList, 'parent'));
if($type == 'product' || $type == 'my') return print(js::reload('parent'));
if($type == 'execution') return print(js::locate($this->createLink('execution', 'bug', "executionID=$objectID")));
if($type == 'project') return print(js::locate($this->createLink('project', 'bug', "projectID=$objectID")));
}
+3 -3
View File
@@ -712,8 +712,8 @@ function notice()
var branch = $('#branch').val();
if(typeof(branch) == 'undefined') branch = 0;
var link = createLink('release', 'create', 'productID=' + $('#product').val() + '&branch=' + branch);
if(config.onlybody != 'yes') link += config.requestType == 'GET' ? '&onlybody=yes' : '?onlybody=yes';
html += '<a href="' + link + '" data-toggle="modal" data-type="iframe" style="padding-right:5px">' + createRelease + '</a> ';
if(config.onlybody != 'yes') link += link.indexOf('?') >= 0 ? '&onlybody=yes' : '?onlybody=yes';
html += '<a href="' + link + '" data-toggle="modal" data-type="iframe" style="padding-right:5px">' + createBuild + '</a> ';
html += '<a href="javascript:loadProductBuilds(' + $('#product').val() + ')">' + refresh + '</a>';
}
else
@@ -722,7 +722,7 @@ function notice()
productID = $('#product').val();
projectID = $('#project').val();
link = createLink('build', 'create','executionID=' + executionID + '&productID=' + productID + '&projectID=' + projectID);
if(config.onlybody != 'yes') link += config.requestType == 'GET' ? '&onlybody=yes' : '?onlybody=yes';
link += link.indexOf('?') >= 0 ? '&onlybody=yes' : '?onlybody=yes';
html += '<a href="' + link + '" data-toggle="modal" data-type="iframe" style="padding-right:5px">' + createBuild + '</a> ';
html += '<a href="javascript:loadExecutionBuilds(' + executionID + ')">' + refresh + '</a>';
}
+2
View File
@@ -137,6 +137,7 @@ $lang->bug->assignToMe = 'AssignedToMe';
$lang->bug->openedByMe = 'ReportedByMe';
$lang->bug->resolvedByMe = 'ResolvedByMe';
$lang->bug->closedByMe = 'ClosedByMe';
$lang->bug->assignedByMe = 'AssignedByMe';
$lang->bug->assignToNull = 'Unassigned';
$lang->bug->unResolved = 'Active';
$lang->bug->toClosed = 'ToBeClosed';
@@ -420,6 +421,7 @@ $lang->bug->featureBar['browse']['unclosed'] = $lang->bug->unclosed;
$lang->bug->featureBar['browse']['openedbyme'] = $lang->bug->openedByMe;
$lang->bug->featureBar['browse']['assigntome'] = $lang->bug->assignToMe;
$lang->bug->featureBar['browse']['resolvedbyme'] = $lang->bug->resolvedByMe;
$lang->bug->featureBar['browse']['assignedbyme'] = $lang->bug->assignedByMe;
$lang->bug->featureBar['browse']['unresolved'] = $lang->bug->unResolved;
$lang->bug->featureBar['browse']['more'] = $lang->more;
+2
View File
@@ -137,6 +137,7 @@ $lang->bug->assignToMe = '指派给我';
$lang->bug->openedByMe = '由我创建';
$lang->bug->resolvedByMe = '由我解决';
$lang->bug->closedByMe = '由我关闭';
$lang->bug->assignedByMe = '由我指派';
$lang->bug->assignToNull = '未指派';
$lang->bug->unResolved = '未解决';
$lang->bug->toClosed = '待关闭';
@@ -420,6 +421,7 @@ $lang->bug->featureBar['browse']['unclosed'] = $lang->bug->unclosed;
$lang->bug->featureBar['browse']['openedbyme'] = $lang->bug->openedByMe;
$lang->bug->featureBar['browse']['assigntome'] = $lang->bug->assignToMe;
$lang->bug->featureBar['browse']['resolvedbyme'] = $lang->bug->resolvedByMe;
$lang->bug->featureBar['browse']['assignedbyme'] = $lang->bug->assignedByMe;
$lang->bug->featureBar['browse']['toclosed'] = $lang->bug->toClosed;
$lang->bug->featureBar['browse']['unresolved'] = $lang->bug->unResolved;
$lang->bug->featureBar['browse']['more'] = $lang->more;
+36 -5
View File
@@ -79,7 +79,7 @@ class bugModel extends model
->remove('files,labels,uid,oldTaskID,contactListMenu,region,lane')
->get();
if($bug->execution != 0) $bug->project = $this->dao->select('parent')->from(TABLE_EXECUTION)->where('id')->eq($bug->execution)->fetch('parent');
if($bug->execution != 0) $bug->project = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($bug->execution)->fetch('project');
/* Check repeat bug. */
$result = $this->loadModel('common')->removeDuplicate('bug', $bug, "product={$bug->product}");
@@ -211,7 +211,7 @@ class bugModel extends model
if(isset($data->lanes[$i])) $bug->laneID = $data->lanes[$i];
if($bug->execution != 0) $bug->project = $this->dao->select('parent')->from(TABLE_EXECUTION)->where('id')->eq($bug->execution)->fetch('parent');
if($bug->execution != 0) $bug->project = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($bug->execution)->fetch('project');
/* Assign the bug to the person in charge of the module. */
if(!empty($moduleOwners[$bug->module]))
@@ -349,7 +349,7 @@ class bugModel extends model
$bug->task = 0;
$bug->pri = 3;
$bug->severity = 3;
$bug->project = $this->dao->select('parent')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->fetch('parent');
$bug->project = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->fetch('project');
$this->dao->insert(TABLE_BUG)->data($bug, $skip = 'gitlab,gitlabProject')->autoCheck()->batchCheck($this->config->bug->create->requiredFields, 'notempty')->exec();
if(!dao::isError()) return $this->dao->lastInsertID();
@@ -396,6 +396,7 @@ class bugModel extends model
elseif($browseType == 'needconfirm') $bugs = $this->getByNeedconfirm($productIDList, $branch, $modules, $executions, $sort, $pager, $projectID);
elseif($browseType == 'bysearch') $bugs = $this->getBySearch($productIDList, $branch, $queryID, $sort, '', $pager, $projectID);
elseif($browseType == 'overduebugs') $bugs = $this->getOverdueBugs($productIDList, $branch, $modules, $executions, $sort, $pager, $projectID);
elseif($browseType == 'assignedbyme') $bugs = $this->getByAssignedbyme($productIDList, $branch, $modules, $executions, $sort, $pager, $projectID);
return $this->checkDelayedBugs($bugs);
}
@@ -2327,7 +2328,7 @@ class bugModel extends model
$commonOption = $this->lang->bug->report->options;
$chartOption->graph->caption = $this->lang->bug->report->charts[$chartType];
if(!isset($chartOption->type)) $chartOption->type = $commonOption->type;
if(!isset($chartOption->type)) $chartOption->type = $commonOption->type;
if(!isset($chartOption->width)) $chartOption->width = $commonOption->width;
if(!isset($chartOption->height)) $chartOption->height = $commonOption->height;
@@ -2646,6 +2647,36 @@ class bugModel extends model
->fetchAll();
}
/**
* Get by assigned by me.
* @param array $productIDList
* @param int|string $branch
* @param array $modules
* @param array $executions
* @param string $sort
* @param object $pager
* @param int $projectID
*
* @access public
* @return array
*/
public function getByAssignedbyme($productIDList, $branch, $modules, $executions, $sort, $pager, $projectID)
{
$actionIDList = $this->dao->select('objectID')->from(TABLE_ACTION)->where('objectType')->eq('bug')->andWhere('action')->eq('assigned')->andWhere('actor')->eq($this->app->user->account)->fetchPairs('objectID', 'objectID');
return $this->dao->select('*')->from(TABLE_BUG)
->where('product')->in($productIDList)
->beginIF($branch !== 'all')->andWhere('branch')->in($branch)->fi()
->beginIF($modules)->andWhere('module')->in($modules)->fi()
->beginIF($projectID)->andWhere('project')->eq($projectID)->fi()
->andWhere('execution')->in(array_keys($executions))
->andWhere('deleted')->eq(0)
->andWhere('status')->ne('closed')
->andWhere('id')->in($actionIDList)
->orderBy($sort)
->page($pager)
->fetchAll();
}
/**
* Get by Sonarqube id.
*
@@ -3282,7 +3313,7 @@ class bugModel extends model
if($type == 'view') $menu .= $this->buildMenu('bug', 'activate', $params, $bug, $type, '', '', "text-success iframe showinonlybody", true);
if($type == 'view' && $this->app->tab != 'product')
{
$menu .= $this->buildMenu('bug', 'toStory', $toStoryParams, $bug, $type, $this->lang->icons['story'], '', '', '', "data-app='product'", $this->lang->bug->toStory);
$menu .= $this->buildMenu('bug', 'toStory', $toStoryParams, $bug, $type, $this->lang->icons['story'], '', '', '', "data-app='qa'", $this->lang->bug->toStory);
$menu .= $this->buildMenu('bug', 'createCase', $convertParams, $bug, $type, 'sitemap');
}
if($type == 'view')
+3 -18
View File
@@ -131,12 +131,7 @@
<td class='<?php echo zget($visibleFields, 'keywords', 'hidden')?>'><?php echo html::input("keywords[$i]", '', "class='form-control'");?></td>
<?php
$this->loadModel('flow');
foreach($extendFields as $extendField)
{
$object = new stdclass();
$object->{$extendField->field} = $extendField->default;
echo "<td" . (($extendField->control == 'select' or $extendField->control == 'multi-select') ? " style='overflow:visible'" : '') . ">" . $this->flow->getFieldControl($extendField, $object, $extendField->field . "[$i]") . "</td>";
}
foreach($extendFields as $extendField) echo "<td" . (($extendField->control == 'select' or $extendField->control == 'multi-select') ? " style='overflow:visible'" : '') . ">" . $this->flow->getFieldControl($extendField, '', $extendField->field . "[$i]") . "</td>";
?>
</tr>
<?php $i++;?>
@@ -186,12 +181,7 @@
<td class='<?php echo zget($visibleFields, 'keywords', 'hidden')?>'><?php echo html::input("keywords[$i]", '', "class='form-control'");?></td>
<?php
$this->loadModel('flow');
foreach($extendFields as $extendField)
{
$object = new stdclass();
$object->{$extendField->field} = $extendField->default;
echo "<td" . (($extendField->control == 'select' or $extendField->control == 'multi-select') ? " style='overflow:visible'" : '') . ">" . $this->flow->getFieldControl($extendField, $object, $extendField->field . "[$i]") . "</td>";
}
foreach($extendFields as $extendField) echo "<td" . (($extendField->control == 'select' or $extendField->control == 'multi-select') ? " style='overflow:visible'" : '') . ">" . $this->flow->getFieldControl($extendField, '', $extendField->field . "[$i]") . "</td>";
?>
</tr>
<?php endfor;?>
@@ -240,12 +230,7 @@
<td class='<?php echo zget($visibleFields, 'keywords', 'hidden')?>'><?php echo html::input("keywords[%s]", '', "class='form-control'");?></td>
<?php
$this->loadModel('flow');
foreach($extendFields as $extendField)
{
$object = new stdclass();
$object->{$extendField->field} = $extendField->default;
echo "<td" . (($extendField->control == 'select' or $extendField->control == 'multi-select') ? " style='overflow:visible'" : '') . ">" . $this->flow->getFieldControl($extendField, $object, $extendField->field . "[%s]") . "</td>";
}
foreach($extendFields as $extendField) echo "<td" . (($extendField->control == 'select' or $extendField->control == 'multi-select') ? " style='overflow:visible'" : '') . ">" . $this->flow->getFieldControl($extendField, '', $extendField->field . "[%s]") . "</td>";
?>
</tr>
</tbody>
+12 -1
View File
@@ -65,8 +65,19 @@
<th class='w-60px text-right'><?php echo $lang->report->percent;?></th>
</tr>
</thead>
<?php
$colorList = array();
if(strpos(strtolower($chartType), 'pri') !== false)
{
$colorList = $config->bug->colorList->pri;
}
elseif(strpos(strtolower($chartType), 'severity') !== false)
{
$colorList = $config->bug->colorList->severity;
}
?>
<?php foreach($datas[$chartType] as $key => $data):?>
<tr>
<tr data-color="<?php echo !empty($colorList) ? zget($colorList, $key, '#C0C0C0') : '';?>">
<td class='chart-color'><i class='chart-color-dot'></i></td>
<td class='chart-label text-left' title='<?php echo isset($data->title) ? $data->title : $data->name;?>'><?php echo $data->name;?></td>
<td class='chart-value text-right'><?php echo $data->value;?></td>
+1 -1
View File
@@ -217,7 +217,7 @@
<th><?php echo $lang->bug->deadline;?></th>
<td>
<?php
if($bug->deadline) echo $bug->deadline;
if($bug->deadline) echo $bug->deadline;
if(isset($bug->delay)) printf($lang->bug->delayWarning, $bug->delay);
?>
</td>
+4 -1
View File
@@ -246,7 +246,10 @@ class buildModel extends model
foreach($releases as $buildID => $releaseName)
{
$branchName = $allBuilds[$buildID]->branchName ? $allBuilds[$buildID]->branchName : $this->lang->branch->main;
$builds[$buildID] = (strpos($params, 'withbranch') !== false ? $branchName . '/' : '') . $releaseName;
if($allBuilds[$buildID]->productType != 'normal')
{
$builds[$buildID] = (strpos($params, 'withbranch') !== false ? $branchName . '/' : '') . $releaseName;
}
}
}
+1 -1
View File
@@ -42,7 +42,7 @@
<?php foreach($allStories as $story):?>
<tr>
<td class='c-id text-left'>
<?php echo html::checkbox('stories', array($story->id => sprintf('%03d', $story->id)), ($story->stage == 'developed' or $story->status == 'closed') ? $story->id : '');?>
<?php echo html::checkbox('stories', array($story->id => sprintf('%03d', $story->id)), (in_array($story->stage, array('developed', 'closed', 'tested'))) ? $story->id : '');?>
</td>
<td><span class='label-pri label-pri-<?php echo $story->pri;?>' title='<?php echo zget($lang->story->priList, $story->pri, $story->pri);?>'><?php echo zget($lang->story->priList, $story->pri, $story->pri)?></span></td>
<td class='text-left nobr' title='<?php echo $story->title?>'>
-1
View File
@@ -251,7 +251,6 @@ class caselib extends control
if(!empty($_POST))
{
$this->loadModel('testcase');
$this->config->testcase->create->requiredFields = $this->config->caselib->createcase->requiredFields;
setcookie('lastLibCaseModule', (int)$this->post->module, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, false);
$caseResult = $this->testcase->create($bugID = 0);
if(!$caseResult or dao::isError()) return print(js::error(dao::getError()));
+3 -4
View File
@@ -258,7 +258,7 @@ class caselibModel extends model
$cases = $this->dao->select('*')->from(TABLE_CASE)->where($caseQuery)
->beginIF($queryLibID != 'all')->andWhere('lib')->eq((int)$libID)->fi()
->beginIF($this->config->systemMode == 'new' and $this->lang->navGroup->caselib != 'qa')->andWhere('project')->eq($this->session->project)->fi()
->beginIF($this->config->systemMode == 'new' and $this->app->tab != 'qa')->andWhere('project')->eq($this->session->project)->fi()
->andWhere('product')->eq(0)
->andWhere('deleted')->eq(0)
->orderBy($sort)->page($pager)->fetchAll();
@@ -282,7 +282,7 @@ class caselibModel extends model
$this->config->testcase->search['params']['lib']['values'] = array('' => '', $libID => $libraries[$libID], 'all' => $this->lang->caselib->all);
$this->config->testcase->search['params']['lib']['operator'] = '=';
$this->config->testcase->search['params']['lib']['control'] = 'select';
$this->config->testcase->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($libID, $viewType = 'caselib');
$this->config->testcase->search['params']['module']['values'] = $this->loadModel('tree')->getOptionMenu($libID, 'caselib');
if(!$this->config->testcase->needReview) unset($this->config->testcase->search['params']['status']['values']['wait']);
unset($this->config->testcase->search['fields']['product']);
unset($this->config->testcase->search['params']['product']);
@@ -586,8 +586,7 @@ class caselibModel extends model
if(dao::isError())
{
echo js::error(dao::getError());
return print(js::reload('parent'));
return helper::end(js::error(dao::getError()));
}
$caseID = $this->dao->lastInsertID();
+1 -1
View File
@@ -25,7 +25,7 @@
<thead>
<tr class='text-center'>
<th class='c-id'><?php echo $lang->idAB;?></th>
<th class='c-module'><?php echo $lang->testcase->module;?></th>
<th class='c-module<?php echo strpos($config->testcase->create->requiredFields, 'module') ? ' required' : '';?>'><?php echo $lang->testcase->module;?></th>
<th class='required'><?php echo $lang->testcase->title;?></th>
<th class='c-status required'><?php echo $lang->testcase->type;?></th>
<th class='c-status'><?php echo $lang->testcase->pri;?></th>
+1 -1
View File
@@ -33,7 +33,7 @@
<?php echo html::select('lib', $libraries, $libID, "onchange='loadLibModules(this.value);' class='form-control chosen'");?>
</div>
</td>
<td style='padding-left:15px;'>
<td style='padding-left:15px;'<?php echo strpos($config->testcase->create->requiredFields, 'module') ? ' class="required"' : '';?>>
<div class='input-group' id='moduleIdBox'>
<span class="input-group-addon w-80px"><?php echo $lang->testcase->module?></span>
<?php
+2 -2
View File
@@ -128,7 +128,7 @@ $lang->my->dividerMenu = ',work,dynamic,';
/* Program menu. */
$lang->program->homeMenu = new stdclass();
$lang->program->homeMenu->browse = array('link' => "{$lang->program->list}|program|browse|", 'alias' => 'create,edit');
$lang->program->homeMenu->browse = array('link' => "{$lang->program->list}|program|browse|", 'alias' => 'create,edit', 'subModule' => 'project');
$lang->program->homeMenu->kanban = array('link' => "{$lang->program->kanban}|program|kanban|");
$lang->program->menu = new stdclass();
@@ -476,7 +476,7 @@ $lang->company->menuOrder[30] = 'addUser';
$lang->admin->menu = new stdclass();
$lang->admin->menu->index = array('link' => "$lang->indexPage|admin|index", 'alias' => 'register,certifytemail,certifyztmobile,ztcompany');
$lang->admin->menu->company = array('link' => "{$lang->personnel->common}|company|browse|", 'subModule' => ',user,dept,group,');
$lang->admin->menu->model = array('link' => "$lang->model|custom|browsestoryconcept|", 'class' => 'dropdown dropdown-hover', 'exclude' => 'custom-index,custom-set,custom-product,custom-execution,custom-required,custom-flow,custom-score,custom-feedback,custom-timezone,custom-mode');
$lang->admin->menu->model = array('link' => "$lang->model|custom|browsestoryconcept|", 'class' => 'dropdown dropdown-hover', 'exclude' => 'custom-index,custom-set,custom-product,custom-execution,custom-kanban,custom-required,custom-flow,custom-score,custom-feedback,custom-timezone,custom-mode');
$lang->admin->menu->custom = array('link' => "{$lang->custom->common}|custom|index", 'exclude' => 'custom-browsestoryconcept,custom-timezone,custom-estimate');
$lang->admin->menu->extension = array('link' => "{$lang->extension->common}|extension|browse", 'subModule' => 'extension');
$lang->admin->menu->dev = array('link' => "$lang->redev|dev|api", 'alias' => 'db', 'subModule' => 'dev,editor,entry');
+31 -9
View File
@@ -450,7 +450,7 @@ class commonModel extends model
echo '<li class="user-tutorial">' . html::a(helper::createLink('tutorial', 'start'), "<i class='icon icon-guide'></i> " . $lang->tutorialAB, '', "class='iframe' data-class-name='modal-inverse' data-width='800' data-headerless='true' data-backdrop='true' data-keyboard='true'") . '</li>';
}
echo '<li>' . html::a(helper::createLink('my', 'preference', '', '', true), "<i class='icon icon-controls'></i> " . $lang->preference, '', "class='iframe' data-width='700'") . '</li>';
echo '<li>' . html::a(helper::createLink('my', 'preference', 'showTip=false', '', true), "<i class='icon icon-controls'></i> " . $lang->preference, '', "class='iframe' data-width='700'") . '</li>';
}
if(common::hasPriv('my', 'changePassword')) echo '<li>' . html::a(helper::createLink('my', 'changepassword', '', '', true), "<i class='icon icon-cog-outline'></i> " . $lang->changePassword, '', "class='iframe' data-width='600'") . '</li>';
@@ -664,7 +664,12 @@ class commonModel extends model
$params = "productID=$productID&branch=&moduleID=0&from=&param=0&storyID=0&extras=from=global";
break;
case 'execution':
$params = "projectID=&executionID=0&copyExecutionID=0&planID=0&confirm=no&productID=0&extra=from=global";
$projectID = 0;
if(in_array($app->tab, array('project', 'execution')) and isset($lang->switcherMenu))
{
$projectID = isset($_SESSION['project']) ? $_SESSION['project'] : 0;
}
$params = "projectID={$projectID}&executionID=0&copyExecutionID=0&planID=0&confirm=no&productID=0&extra=from=global";
break;
case 'product':
$params = "programID=&extra=from=global";
@@ -913,6 +918,9 @@ class commonModel extends model
public static function getMainNavList($moduleName)
{
global $lang;
global $app;
$app->loadLang('my');
$menuOrder = $lang->mainNav->menuOrder;
ksort($menuOrder);
@@ -958,8 +966,24 @@ class commonModel extends model
}
}
/* Check whether other preference item under the module have permissions. If yes, point to other methods. */
$moduleLinkList = $currentModule . 'LinkList';
if(!$display and isset($lang->my->$moduleLinkList))
{
foreach($lang->my->$moduleLinkList as $key => $linkList)
{
$method = explode('-', $key)[1];
if(common::hasPriv($currentModule, $method))
{
$display = true;
$currentMethod = $method;
break;
}
}
}
/* Check whether other methods under the module have permissions. If yes, point to other methods. */
if($display == false and isset($lang->$currentModule->menu))
if($display == false and isset($lang->$currentModule->menu) and !in_array($currentModule, array('program', 'product', 'project', 'execution')))
{
foreach($lang->$currentModule->menu as $menu)
{
@@ -2216,7 +2240,7 @@ EOD;
{
if($this->app->getModuleName() == 'upgrade' and $this->session->upgrading) return false;
$statusFile = $this->app->getAppRoot() . 'www' . DIRECTORY_SEPARATOR . 'ok.txt';
$statusFile = $this->app->getAppRoot() . 'www' . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'ok.txt';
return (!is_file($statusFile) or (time() - filemtime($statusFile)) > 3600) ? $statusFile : false;
}
@@ -3250,13 +3274,11 @@ EOD;
if(empty($markdown)) return false;
global $app;
$app->loadClass('parsedownextraplugin');
$app->loadClass('michelf');
$Parsedown = new parsedownextraplugin;
$Michelf = new michelf;
$Parsedown->voidElementSuffix = '>'; // HTML5
return $Parsedown->text($markdown);
return $Michelf->parse($markdown);
}
}
+9
View File
@@ -78,5 +78,14 @@ $extHookRule = $extensionRoot . 'custom/common/ext/view/footer.*.hook.php';
$extHookFiles = glob($extHookRule);
if($extHookFiles) foreach($extHookFiles as $extHookFile) include $extHookFile;
?>
<?php if($config->debug > 2 and $config->tabSession): ?>
<div id="tid" style="position:fixed;right:0;bottom:0;z-index:10000">
<code class="bg-red">tsid=<?php if(empty($_GET['tid'])) echo session_id(); else echo md5(session_id() . $_GET['tid']);?></code>
<?php if(!empty($_GET['tid'])): ?>
<code class="bg-yellow">servertid=<?php echo $_GET['tid'];?></code>
<?php endif; ?>
<code class="bg-green">sid=<?php echo session_id();?></code>
</div>
<?php endif; ?>
</body>
</html>
-1
View File
@@ -19,7 +19,6 @@ $onlybody = zget($_GET, 'onlybody', 'no');
<?php
echo html::title($title . ' - ' . $lang->zentaoPMS);
js::exportConfigVars();
echo '<script>config.onlybody = "' . $onlybody . '";</script>';
if($config->debug)
{
$timestamp = time();
+1 -1
View File
@@ -857,7 +857,7 @@ class convertModel extends model
$bug->openedBy = $this->getJiraAccount($data->CREATOR, $method);
$bug->openedDate = substr($data->CREATED, 0, 19);
$bug->openedBuild = 'trunk';
$bug->assignedTo = $this->getJiraAccount($data->ASSIGNEE, $method);
$bug->assignedTo = $bug->status == 'closed' ? 'closed' : $this->getJiraAccount($data->ASSIGNEE, $method);
if($data->RESOLUTION)
{
+2 -2
View File
@@ -56,8 +56,8 @@ $config->custom->fieldList['build'] = 'scmPath,filePath,desc';
$config->custom->fieldList['bug']['create'] = 'module,project,deadline,type,os,browser,severity,pri,steps,keywords';
$config->custom->fieldList['bug']['edit'] = 'plan,project,assignedTo,deadline,type,os,browser,severity,pri,steps,keywords';
$config->custom->fieldList['bug']['resolve'] = 'resolvedBuild,resolvedDate,assignedTo,comment';
$config->custom->fieldList['testcase']['create'] = 'stage,story,pri,precondition,keywords';
$config->custom->fieldList['testcase']['edit'] = 'stage,story,pri,precondition,keywords,status';
$config->custom->fieldList['testcase']['create'] = 'stage,story,pri,precondition,keywords,module';
$config->custom->fieldList['testcase']['edit'] = 'stage,story,pri,precondition,keywords,status,module';
$config->custom->fieldList['testsuite'] = 'desc';
$config->custom->fieldList['caselib'] = 'desc';
$config->custom->fieldList['testcase']['createcase'] = 'lib,stage,pri,precondition,keywords';
+43 -2
View File
@@ -71,7 +71,18 @@ class custom extends control
if(($module == 'story' or $module == 'testcase') and $field == 'review')
{
$this->app->loadConfig($module);
$this->view->users = $this->loadModel('user')->getPairs('noclosed|nodeleted');
$this->loadModel('user');
if($module == 'story')
{
$this->view->depts = $this->loadModel('dept')->getDeptPairs();
$this->view->forceReviewAll = zget($this->config->$module, 'forceReviewAll', 'false');
$this->view->forceReview = zget($this->config->$module, 'forceReview', '');
$this->view->forceReviewRoles = zget($this->config->$module, 'forceReviewRoles', '');
$this->view->forceReviewDepts = zget($this->config->$module, 'forceReviewDepts', '');
}
$this->view->users = $module == 'story' ? $this->user->getCanCreateStoryUsers() : $this->user->getPairs('noclosed|nodeleted');
$this->view->needReview = zget($this->config->$module, 'needReview', 1);
$this->view->forceReview = zget($this->config->$module, 'forceReview', '');
$this->view->forceNotReview = zget($this->config->$module, 'forceNotReview', '');
@@ -112,7 +123,16 @@ class custom extends control
}
elseif($module == 'story' and $field == 'review')
{
$data = fixer::input('post')->join('forceReview', ',')->get();
$data = fixer::input('post')
->setDefault('forceReviewAll', 0)
->setDefault('forceReviewDepts', '')
->join('forceReview', ',')
->join('forceReviewRoles', ',')
->join('forceReviewDepts', ',')
->join('forceReviewAll', ',')
->setIF(isset($this->post->forceReviewAll), 'forceReviewAll', 1)
->get();
$this->loadModel('setting')->setItems("system.$module@{$this->config->vision}", $data);
}
elseif($module == 'story' and $field == 'reviewRules')
@@ -518,6 +538,27 @@ class custom extends control
$this->display();
}
/**
* Set whether the kanban is read-only.
*
* @access public
* @return void
*/
public function kanban()
{
if($_POST)
{
$this->loadModel('setting')->setItem("system.common.CRKanban@{$this->config->vision}", $this->post->kanban);
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'reload'));
}
$this->view->title = $this->lang->custom->kanban;
$this->view->position[] = $this->lang->custom->common;
$this->view->position[] = $this->view->title;
$this->display();
}
/**
* Set flow.
*
+13
View File
@@ -49,6 +49,11 @@ $lang->custom->switch = "Switch";
$lang->custom->oneUnit = "One {$lang->hourCommon}";
$lang->custom->convertRelationTitle = "Please firstly set the conversion factor from {$lang->hourCommon} to %s";
$lang->custom->superReviewers = "Super Reviewer";
$lang->custom->kanban = "Kanban";
$lang->custom->allUsers = 'All Users';
$lang->custom->account = 'Users';
$lang->custom->role = 'Role';
$lang->custom->dept = 'Dept';
if($config->systemMode == 'new') $lang->custom->execution = 'Execution';
if($config->systemMode == 'classic' || !$config->systemMode) $lang->custom->execution = $lang->executionCommon;
@@ -79,11 +84,13 @@ $lang->custom->saveTips = 'After clicking save, the current %s will b
$lang->custom->numberError = 'The interval must be greater than zero!';
$lang->custom->closedExecution = 'Closed ' . $lang->executionCommon;
$lang->custom->closedKanban = 'Closed ' . $lang->custom->kanban;
$lang->custom->closedProduct = 'Closed ' . $lang->productCommon;
if($config->systemMode == 'new') $lang->custom->object['project'] = 'Project';
$lang->custom->object['product'] = $lang->productCommon;
$lang->custom->object['execution'] = $lang->custom->execution;
$lang->custom->object['kanban'] = $lang->custom->kanban;
$lang->custom->object['story'] = 'Story';
$lang->custom->object['task'] = 'Task';
$lang->custom->object['bug'] = 'Bug';
@@ -174,9 +181,12 @@ $lang->custom->notice->conceptResult = 'According to your preference, <b>
$lang->custom->notice->conceptPath = 'Go to Admin -> Custom -> Concept to set it.';
$lang->custom->notice->readOnlyOfProduct = 'If Change Forbidden, any change on stories, bugs, cases, efforts, releases and plans of the closed product is also forbidden.';
$lang->custom->notice->readOnlyOfExecution = "If Change Forbidden, any change on tasks, builds, efforts and stories of the closed {$lang->executionCommon} is also forbidden.";
$lang->custom->notice->readOnlyOfKanban = "If Change Forbidden, any change on kanban card and related operations of {$lang->custom->kanban} is also forbidden.";
$lang->custom->notice->URSREmpty = 'Custom requirement name can not be empty!';
$lang->custom->notice->confirmDelete = 'Are you sure you want to delete it?';
$lang->custom->notice->confirmReviewCase = 'Set the case in Wait to Normal?';
$lang->custom->notice->storyReviewTip = 'After selecting by individual, position, and department, take the union of these three filters. ';
$lang->custom->notice->selectAllTip = 'After selecting all people, the reviewers will be emptied and grayed out while hiding their positions and departments.';
$lang->custom->notice->indexPage['product'] = "ZenTao 8.2+ has Product Home. Do you want to go to Product Home?";
$lang->custom->notice->indexPage['project'] = "ZenTao 8.2+ has Project Home. Do you want to go to Project Home?";
@@ -232,6 +242,9 @@ $lang->custom->CRProduct[0] = 'Change Forbidden';
$lang->custom->CRExecution[1] = 'Change Allowed';
$lang->custom->CRExecution[0] = 'Change Forbidden';
$lang->custom->CRKanban[1] = 'Change Allowed';
$lang->custom->CRKanban[0] = 'Change Forbidden';
$lang->custom->moduleName['product'] = $lang->productCommon;
$lang->custom->moduleName['productplan'] = 'Plan';
$lang->custom->moduleName['execution'] = $lang->custom->execution;
+13
View File
@@ -49,6 +49,11 @@ $lang->custom->switch = "切换";
$lang->custom->oneUnit = "一个{$lang->hourCommon}";
$lang->custom->convertRelationTitle = "请先设置{$lang->hourCommon}转换为%s的换算系数";
$lang->custom->superReviewers = "超级评审人";
$lang->custom->kanban = "看板";
$lang->custom->allUsers = '所有人员';
$lang->custom->account = '人员';
$lang->custom->role = '职位';
$lang->custom->dept = '部门';
if($config->systemMode == 'new') $lang->custom->execution = '执行';
if($config->systemMode == 'classic' || !$config->systemMode) $lang->custom->execution = $lang->executionCommon;
@@ -79,11 +84,13 @@ $lang->custom->saveTips = '点击保存后,则以当前%s为默认
$lang->custom->numberError = '区间必须大于零';
$lang->custom->closedExecution = '已关闭' . $lang->custom->execution;
$lang->custom->closedKanban = '已关闭' . $lang->custom->kanban;
$lang->custom->closedProduct = '已关闭' . $lang->productCommon;
if($config->systemMode == 'new') $lang->custom->object['project'] = '项目';
$lang->custom->object['product'] = $lang->productCommon;
$lang->custom->object['execution'] = $lang->custom->execution;
$lang->custom->object['kanban'] = $lang->custom->kanban;
$lang->custom->object['story'] = $lang->SRCommon;
$lang->custom->object['task'] = '任务';
$lang->custom->object['bug'] = 'Bug';
@@ -174,9 +181,12 @@ $lang->custom->notice->conceptResult = '我们已经根据您的选择为
$lang->custom->notice->conceptPath = '您可以在:后台 -> 自定义 -> 流程页面修改。';
$lang->custom->notice->readOnlyOfProduct = '禁止修改后,已关闭' . $lang->productCommon . '下的' . $lang->SRCommon . '、Bug、用例、日志、发布、计划都禁止修改。';
$lang->custom->notice->readOnlyOfExecution = "禁止修改后,已关闭{$lang->custom->execution}下的任务、版本、日志以及关联需求都禁止修改。";
$lang->custom->notice->readOnlyOfKanban = "禁止修改后,已关闭{$lang->custom->kanban}下的卡片以及相关设置都禁止修改。";
$lang->custom->notice->URSREmpty = '自定义需求名称不能为空!';
$lang->custom->notice->confirmDelete = '您确定要删除吗?';
$lang->custom->notice->confirmReviewCase = '是否将待评审的用例修改为正常状态?';
$lang->custom->notice->storyReviewTip = '按人员、职位、部门勾选后,取所有人员的并集。';
$lang->custom->notice->selectAllTip = '勾选所有人员后,会清空并置灰评审人员,同时隐藏职位、部门。';
$lang->custom->notice->indexPage['product'] = "从8.2版本起增加了产品主页视图,是否默认进入产品主页?";
$lang->custom->notice->indexPage['project'] = "从8.2版本起增加了项目主页视图,是否默认进入项目主页?";
@@ -232,6 +242,9 @@ $lang->custom->CRProduct[0] = '禁止修改';
$lang->custom->CRExecution[1] = '允许修改';
$lang->custom->CRExecution[0] = '禁止修改';
$lang->custom->CRKanban[1] = '允许修改';
$lang->custom->CRKanban[0] = '禁止修改';
$lang->custom->moduleName['product'] = $lang->productCommon;
$lang->custom->moduleName['productplan'] = '计划';
$lang->custom->moduleName['execution'] = $lang->custom->execution;
+2 -2
View File
@@ -5,8 +5,8 @@
foreach($lang->custom->object as $object => $name)
{
if(strpos('story|todo|block', $object) !== false) echo "<span class='divider'></span>";
if(strpos('execution|product', $object) !== false) common::printLink('custom', $object, "", "<span class='text'>{$lang->custom->$object}</span>", '', "class='btn btn-link' id='{$object}Tab'");
if(strpos('execution|product', $object) === false) common::printLink('custom', 'set', "module=$object&field=" . key($lang->custom->{$object}->fields), "<span class='text'>{$name}</span>", '', "class='btn btn-link' id='{$object}Tab'");
if(strpos('execution|product|kanban', $object) !== false) common::printLink('custom', $object, "", "<span class='text'>{$lang->custom->$object}</span>", '', "class='btn btn-link' id='{$object}Tab'");
if(strpos('execution|product|kanban', $object) === false) common::printLink('custom', 'set', "module=$object&field=" . key($lang->custom->{$object}->fields), "<span class='text'>{$name}</span>", '', "class='btn btn-link' id='{$object}Tab'");
if($object == 'user') common::printLink('custom', 'required', "", "<span class='text'>{$lang->custom->required}</span>", '', "class='btn btn-link' id='requiredTab'");
}
+41
View File
@@ -0,0 +1,41 @@
<?php
/**
* The kanban view file of custom module of ZenTaoPMS.
* @copyright Copyright 2009-2020 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @license ZPL (http://zpl.pub/page/zplv12.html)
* @author Liyuchun <liyuchun@cnezsoft.com>
* @package custom
* @version $Id$
* @link http://www.zentao.net
*/
?>
<?php include 'header.html.php';?>
<div id='mainContent' class='main-content'>
<form class="load-indicator main-form form-ajax" method='post'>
<table class='table table-form'>
<tr>
<th class='w-150px'><?php echo $lang->custom->closedKanban;?></th>
<td class='w-300px text-left'>
<?php $checkedKey = isset($config->CRKanban) ? $config->CRKanban : 1;?>
<?php foreach($lang->custom->CRKanban as $key => $value):?>
<label class="radio-inline"><input type="radio" name="kanban" value="<?php echo $key?>"<?php echo $key == $checkedKey ? " checked='checked'" : ''?> id="kanban<?php echo $key;?>"><?php echo $value;?></label>
<?php endforeach;?>
</td>
<td><span class='alert alert-info no-margin'><?php echo $lang->custom->notice->readOnlyOfKanban;?></span></td>
</tr>
<tr>
<th></th>
<td class='form-actions'>
<?php echo html::submitButton();?>
</td>
</tr>
</table>
</form>
</div>
<script>
$(function()
{
$('#mainMenu #kanbanTab').addClass('btn-active-text');
})
</script>
<?php include '../../common/view/footer.html.php';?>
+74 -5
View File
@@ -36,6 +36,18 @@ EOT;
<style>
.checkbox-primary {width: 170px; margin: 0 10px 10px 0; display: inline-block;}
</style>
<?php if($module == 'story' and $field == 'review'):?>
<style>
.reviewBox > th {width: 95px !important;}
.reviewBox > td {width: 500px !important;}
.checkbox-primary {margin-bottom: 0px; width: 82px !important;}
.storyReviewTip {padding-left: 95px;}
<?php if($app->getClientLang() != 'zh-cn' and $app->getClientLang() != 'zh-tw'):?>
.reviewBox > th {width: 160px !important;}
.storyReviewTip {padding-left: 160px;}
<?php endif;?>
</style>
<?php endif;?>
<div id='mainContent' class='main-row'>
<div class='side-col' id='sidebar'>
<div class='cell'>
@@ -88,18 +100,39 @@ EOT;
</tr>
</table>
<?php elseif(($module == 'story' or $module == 'testcase') and $field == 'review'):?>
<table class='table table-form mw-800px'>
<tr>
<table class='table table-form'>
<tr class='reviewBox'>
<th class='thWidth'><?php echo $lang->custom->storyReview;?></th>
<td><?php echo html::radio('needReview', $lang->custom->reviewList, $needReview);?></td>
<td></td>
</tr>
<tr <?php if($needReview and $module == 'testcase') echo "class='hidden'"?>>
<?php if($module == 'story'):?>
<tr>
<?php $space = ($app->getClientLang() != 'zh-cn' and $app->getClientLang() != 'zh-tw') ? ' ': '';?>
<td colspan='3'><div class='storyReviewTip'><?php echo sprintf($lang->custom->notice->forceReview, $lang->$module->common) . $lang->custom->notice->storyReviewTip;?></td>
</tr>
<tr id='userBox'>
<th><?php echo $lang->custom->forceReview . $space . $lang->custom->account;?></th>
<td><?php echo html::select('forceReview[]', $users, $forceReview, "class='form-control chosen' multiple");?></td>
<td>
<?php echo html::checkbox('forceReviewAll', array('1' => $lang->custom->allUsers), $forceReviewAll);?>
<icon class='icon icon-help' data-toggle='popover' data-trigger='focus hover' data-placement='right' data-tip-class='text-muted popover-sm' data-content="<?php echo $lang->custom->notice->selectAllTip;?>"></icon>
</td>
</tr>
<tr id='roleBox'>
<th><?php echo $lang->custom->forceReview . $space . $lang->custom->role;?></th>
<td><?php echo html::select('forceReviewRoles[]', $lang->user->roleList, $forceReviewRoles, "class='form-control chosen' multiple");?></td>
</tr>
<tr id='deptBox'>
<th><?php echo $lang->custom->forceReview . $space . $lang->custom->dept;?></th>
<td><?php echo html::select('forceReviewDepts[]', $depts, $forceReviewDepts, "class='form-control chosen' multiple");?></td>
</tr>
<?php endif;?>
<?php if($module == 'testcase'):?>
<tr <?php if($needReview) echo "class='hidden'"?>>
<th><?php echo $lang->custom->forceReview;?></th>
<td><?php echo html::select('forceReview[]', $users, $forceReview, "class='form-control chosen' multiple");?></td>
<td style='width:300px'><?php printf($lang->custom->notice->forceReview, $lang->$module->common);?></td>
</tr>
<?php if($module == 'testcase'):?>
<tr <?php if(!$needReview) echo "class='hidden'"?>>
<th><?php echo $lang->custom->forceNotReview;?></th>
<td><?php echo html::select('forceNotReview[]', $users, $forceNotReview, "class='form-control chosen' multiple");?></td>
@@ -231,6 +264,42 @@ EOT;
</form>
</div>
</div>
<?php if($module == 'story' and $field == 'review'):?>
<script>
$(function()
{
$('[data-toggle="popover"]').popover();
toggleBox($("input[name^='forceReviewAll']").prop('checked'));
$("input[name^='forceReviewAll']").click(function()
{
toggleBox($(this).prop('checked'));
});
/**
* Toggle box.
*
* @param bool $checked
* @access public
* @return void
*/
function toggleBox(checked)
{
$('#roleBox').toggleClass('hidden', checked);
$('#deptBox').toggleClass('hidden', checked);
if(checked)
{
$('#forceReview').val('').attr('disabled', 'disabled').trigger('chosen:updated');
}
else
{
$('#forceReview').removeAttr('disabled', 'disabled').trigger('chosen:updated');
}
}
})
</script>
<?php endif;?>
<?php if($module == 'testcase' and $field == 'review'):?>
<script>
$(function()
+2
View File
@@ -3,3 +3,5 @@ $config->datatable->moduleAlias['product-browse'] = 'story';
$config->datatable->moduleAlias['execution-task'] = 'task';
$config->datatable->moduleAlias['testtask-cases'] = 'testcase';
$config->datatable->moduleAlias['program-project'] = 'project';
$config->datatable->moduleAlias['project-bug'] = 'bug';
$config->datatable->moduleAlias['execution-bug'] = 'bug';
+9 -11
View File
@@ -250,17 +250,18 @@ class doc extends control
*
* @param int $libID
* @param string $confirm yes|no
* @param string $from lib|book
* @param string $type lib|book
* @param string $from tableContents|objectLibs
* @access public
* @return void
*/
public function deleteLib($libID, $confirm = 'no', $from = 'lib')
public function deleteLib($libID, $confirm = 'no', $type = 'lib', $from = 'objectLibs')
{
if($libID == 'product' or $libID == 'execution') return;
if($confirm == 'no')
{
$deleteTip = $from == 'book' ? $this->lang->doc->confirmDeleteBook : $this->lang->doc->confirmDeleteLib;
return print(js::confirm($deleteTip, $this->createLink('doc', 'deleteLib', "libID=$libID&confirm=yes")));
$deleteTip = $type == 'book' ? $this->lang->doc->confirmDeleteBook : $this->lang->doc->confirmDeleteLib;
return print(js::confirm($deleteTip, $this->createLink('doc', 'deleteLib', "libID=$libID&confirm=yes&type=$lib&from=$from")));
}
else
{
@@ -274,13 +275,9 @@ class doc extends control
return print(js::locate($this->createLink('doc', 'objectLibs', 'type=book'), 'parent.parent'));
}
$browseLink = $this->createLink('doc', 'index');
if(in_array($this->app->tab, array('product', 'project', 'execution')))
{
$objectType = $lib->type;
$objectID = $lib->{$objectType};
$browseLink = $this->createLink('doc', 'objectLibs', "type=$objectType&objectID=$objectID");
}
$objectType = $lib->type;
$objectID = strpos(',product,project,execution,', ",$objectType,") !== false ? $lib->{$objectType} : 0;
$browseLink = $this->createLink('doc', $from, "type=$objectType&objectID=$objectID");
return print(js::locate($browseLink, 'parent'));
}
@@ -1113,6 +1110,7 @@ class doc extends control
public function tableContents($type, $objectID = 0, $libID = 0)
{
list($libs, $libID, $object, $objectID) = $this->doc->setMenuByType($type, $objectID, $libID);
$this->session->set('createProjectLocate', $this->app->getURI(true), 'doc');
$libID = (int)$libID;
+10 -5
View File
@@ -1,8 +1,5 @@
<?php js::set('confirmDelete', $lang->doc->confirmDelete);?>
<?php
$sessionString = $config->requestType == 'PATH_INFO' ? '?' : '&';
$sessionString .= session_name() . '=' . session_id();
?>
<?php $sessionString = session_name() . '=' . session_id();?>
<div id="mainContent" class="main-row">
<div class="main-col col-8">
<div class="cell" id="content">
@@ -115,7 +112,15 @@ $sessionString .= session_name() . '=' . session_id();
<img onload="setImageSize(this, 0)" src="<?php echo $this->createLink('file', 'read', "fileID={$file->id}");?>" alt="<?php echo $file->title?>" title="<?php echo $file->title;?>">
</a>
<span class='right-icon'>
<?php if(common::hasPriv('file', 'download')) echo html::a($this->createLink('file', 'download', 'fileID=' . $file->id) . $sessionString, "<i class='icon icon-import'></i>", '', "class='btn-icon' style='margin-right: 10px;' title=\"{$lang->doc->download}\"");?>
<?php
if(common::hasPriv('file', 'download'))
{
$downloadLink = $this->createLink('file', 'download', 'fileID=' . $file->id);
$downloadLink .= strpos($downloadLink, '?') === false ? '?' : '&';
$downloadLink .= $sessionString;
echo html::a($downloadLink, "<i class='icon icon-import'></i>", '', "class='btn-icon' style='margin-right: 10px;' title=\"{$lang->doc->download}\"");
}
?>
<?php if(common::hasPriv('doc', 'deleteFile')) echo html::a('###', "<i class='icon icon-trash'></i>", '', "class='btn-icon' title=\"{$lang->doc->deleteFile}\" onclick='deleteFile($file->id)'");?>
</span>
</div>
+13 -13
View File
@@ -80,10 +80,10 @@
$imageWidth = $imageSize ? $imageSize[0] : 0;
}
$sessionString = $config->requestType == 'PATH_INFO' ? '?' : '&';
$sessionString .= session_name() . '=' . session_id();
$fileID = $file->id;
$url = helper::createLink('file', 'download', 'fileID=' . $fileID) . $sessionString ;
$fileID = $file->id;
$url = helper::createLink('file', 'download', 'fileID=' . $fileID);
$url .= strpos($url, '?') === false ? '?' : '&';
$url .= session_name() . '=' . session_id();
?>
<div class='file'>
<a href='<?php echo $url;?>' title='<?php echo $file->title;?>' target='_blank' onclick="return downloadFile(<?php echo $file->id?>, '<?php echo $file->extension?>', <?php echo $imageWidth?>)">
@@ -125,18 +125,18 @@
<?php js::set('type', $type);?>
<?php js::set('tab', $this->app->tab);?>
<script>
<?php
$sessionString = $config->requestType == 'PATH_INFO' ? '?' : '&';
$sessionString .= session_name() . '=' . session_id();
?>
<?php $sessionString = session_name() . '=' . session_id();?>
function downloadFile(fileID, extension, imageWidth)
{
if(!fileID) return;
var fileTypes = 'jpg,jpeg,gif,png,bmp';
var sessionString = '<?php echo $sessionString;?>';
var windowWidth = $(window).width();
var url = createLink('file', 'download', 'fileID=' + fileID + '&mouse=left') + sessionString;
width = (windowWidth > imageWidth) ? ((imageWidth < windowWidth*0.5) ? windowWidth*0.5 : imageWidth) : windowWidth;
var fileTypes = 'jpg,jpeg,gif,png,bmp';
var windowWidth = $(window).width();
var url = createLink('file', 'download', 'fileID=' + fileID + '&mouse=left');
url += url.indexOf('?') >= 0 ? '&' : '?';
url += '<?php echo $sessionString;?>';
width = (windowWidth > imageWidth) ? ((imageWidth < windowWidth * 0.5) ? windowWidth * 0.5 : imageWidth) : windowWidth;
if(fileTypes.indexOf(extension) >= 0)
{
$('<a>').modalTrigger({url: url, type: 'iframe', width: width}).trigger('click');
+1 -1
View File
@@ -48,7 +48,7 @@ if(empty($type)) $type = 'product';
echo "<li class='divider'></li>";
}
if($canEditLib) echo '<li>' . html::a($this->createLink('doc', 'editLib', "rootID=$libID"), '<i class="icon-edit"></i> ' . $lang->doc->editLib, '', "class='iframe'") . '</li>';
if($canDeleteLib) echo '<li>' . html::a($this->createLink('doc', 'deleteLib', "rootID=$libID"), '<i class="icon-trash"></i> ' . $lang->doc->deleteLib, 'hiddenwin') . '</li>';
if($canDeleteLib) echo '<li>' . html::a($this->createLink('doc', 'deleteLib', "rootID=$libID&confirm=no&type=lib&from=tableContents"), '<i class="icon-trash"></i> ' . $lang->doc->deleteLib, 'hiddenwin') . '</li>';
echo '</ul></div>';
}
+10 -5
View File
@@ -15,10 +15,7 @@
<?php echo css::internal($keTableCSS);?>
<style>.detail-content .file-image {padding: 0 50px 0 10px;}</style>
<?php $browseLink = $this->session->docList ? $this->session->docList : inlink('browse', 'browseType=byediteddate');?>
<?php
$sessionString = $config->requestType == 'PATH_INFO' ? '?' : '&';
$sessionString .= session_name() . '=' . session_id();
?>
<?php $sessionString = session_name() . '=' . session_id();?>
<?php
js::set('fullscreen', $lang->fullscreen);
js::set('retrack', $lang->retrack);
@@ -130,7 +127,15 @@ js::set('docID', $doc->id);
<img onload="setImageSize(this, 0)" src="<?php echo $this->createLink('file', 'read', "fileID={$file->id}");?>" alt="<?php echo $file->title?>" title="<?php echo $file->title;?>">
</a>
<span class='right-icon'>
<?php if(common::hasPriv('file', 'download')) echo html::a($this->createLink('file', 'download', 'fileID=' . $file->id) . $sessionString, "<i class='icon icon-import'></i>", '', "class='btn-icon' style='margin-right: 10px;' title=\"{$lang->doc->download}\"");?>
<?php
if(common::hasPriv('file', 'download'))
{
$downloadLink = $this->createLink('file', 'download', 'fileID=' . $file->id);
$downloadLink .= strpos($downloadLink, '?') === false ? '?' : '&';
$downloadLink .= $sessionString;
echo html::a($downloadLink, "<i class='icon icon-import'></i>", '', "class='btn-icon' style='margin-right: 10px;' title=\"{$lang->doc->download}\"");
}
?>
<?php if(common::hasPriv('doc', 'deleteFile')) echo html::a('###', "<i class='icon icon-trash'></i>", '', "class='btn-icon' title=\"{$lang->doc->deleteFile}\" onclick='deleteFile($file->id)'");?>
</span>
</div>
+1 -1
View File
@@ -7,7 +7,7 @@ $config->execution->weekend = '2';
$config->execution->ownerFields = array('PO', 'PM', 'QD', 'RD');
$config->execution->list = new stdclass();
$config->execution->list->exportFields = 'id,name,projectName,code,PM,end,status,totalEstimate,totalConsumed,totalLeft,progress';
$config->execution->list->exportFields = 'id,name,projectName,code,PM,begin,end,status,totalEstimate,totalConsumed,totalLeft,progress';
$config->execution->modelList['scrum'] = 'sprint';
$config->execution->modelList['waterfall'] = 'stage';
+86 -25
View File
@@ -880,9 +880,10 @@ class execution extends control
$this->loadModel('bug');
$this->loadModel('user');
$this->loadModel('product');
$this->loadModel('datatable');
/* Save session. */
$this->session->set('bugList', $this->app->getURI(true), 'execution');
$this->session->set('bugList', $this->app->getURI(true), 'qa');
$type = strtolower($type);
$queryID = ($type == 'bysearch') ? (int)$param : 0;
@@ -892,7 +893,7 @@ class execution extends control
$branchID = isset($products[$productID]) ? current($products[$productID]->branches) : 0;
$productPairs = array('0' => $this->lang->product->all);
foreach($products as $product) $productPairs[$product->id] = $product->name;
foreach($products as $productData) $productPairs[$productData->id] = $productData->name;
$this->lang->modulePageNav = $this->product->select($productPairs, $productID, 'execution', 'bug', $executionID, $branchID, 0, '', false);
/* Header and position. */
@@ -921,22 +922,61 @@ class execution extends control
$actionURL = $this->createLink('execution', 'bug', "executionID=$executionID&productID=$productID&orderBy=$orderBy&build=$build&type=bysearch&queryID=myQueryID");
$this->execution->buildBugSearchForm($products, $queryID, $actionURL);
$product = $this->loadModel('product')->getById($productID);
$showBranch = false;
$branchOption = array();
$branchTagOption = array();
if($product and $product->type != 'normal')
{
/* Display of branch label. */
$showBranch = $this->loadModel('branch')->showBranch($productID);
/* Display status of branch. */
$branches = $this->loadModel('branch')->getList($productID, 0, 'all');
foreach($branches as $branchInfo)
{
$branchOption[$branchInfo->id] = $branchInfo->name;
$branchTagOption[$branchInfo->id] = $branchInfo->name . ($branchInfo->status == 'closed' ? ' (' . $this->lang->branch->statusList['closed'] . ')' : '');
}
}
/* Get story and task id list. */
$storyIdList = $taskIdList = array();
foreach($bugs as $bug)
{
if($bug->story) $storyIdList[$bug->story] = $bug->story;
if($bug->task) $taskIdList[$bug->task] = $bug->task;
if($bug->toTask) $taskIdList[$bug->toTask] = $bug->toTask;
}
$storyList = $storyIdList ? $this->loadModel('story')->getByList($storyIdList) : array();
$taskList = $taskIdList ? $this->loadModel('task')->getByList($taskIdList) : array();
$showModule = !empty($this->config->datatable->bugBrowse->showModule) ? $this->config->datatable->bugBrowse->showModule : '';
/* Assign. */
$this->view->title = $title;
$this->view->position = $position;
$this->view->bugs = $bugs;
$this->view->tabID = 'bug';
$this->view->build = $this->loadModel('build')->getById($build);
$this->view->buildID = $this->view->build ? $this->view->build->id : 0;
$this->view->pager = $pager;
$this->view->orderBy = $orderBy;
$this->view->users = $users;
$this->view->productID = $productID;
$this->view->branchID = empty($this->view->build->branch) ? $branchID : $this->view->build->branch;
$this->view->memberPairs = $memberPairs;
$this->view->type = $type;
$this->view->param = $param;
$this->view->defaultProduct = (empty($productID) and !empty($products)) ? current(array_keys($products)) : $productID;
$this->view->title = $title;
$this->view->position = $position;
$this->view->bugs = $bugs;
$this->view->tabID = 'bug';
$this->view->build = $this->loadModel('build')->getById($build);
$this->view->buildID = $this->view->build ? $this->view->build->id : 0;
$this->view->pager = $pager;
$this->view->orderBy = $orderBy;
$this->view->users = $users;
$this->view->productID = $productID;
$this->view->branchID = empty($this->view->build->branch) ? $branchID : $this->view->build->branch;
$this->view->memberPairs = $memberPairs;
$this->view->type = $type;
$this->view->param = $param;
$this->view->defaultProduct = (empty($productID) and !empty($products)) ? current(array_keys($products)) : $productID;
$this->view->builds = $this->loadModel('build')->getBuildPairs($productID);
$this->view->branchOption = $branchOption;
$this->view->branchTagOption = $branchTagOption;
$this->view->modulePairs = $showModule ? $this->loadModel('tree')->getModulePairs($productID, 'bug', $showModule) : array();
$this->view->plans = $this->loadModel('productplan')->getPairs($productID);
$this->view->stories = $storyList;
$this->view->tasks = $taskList;
$this->view->projectPairs = $this->loadModel('project')->getPairsByProgram();
$this->display();
}
@@ -1453,7 +1493,7 @@ class execution extends control
$this->view->name = $name;
$this->view->code = $code;
$this->view->team = $team;
$this->view->teams = array(0 => '') + $this->execution->getCanCopyObjects((int)$projectID);
$this->view->teams = array(0 => '', $projectID => (isset($project->name) ? $project->name : '')) + $this->execution->getCanCopyObjects((int)$projectID);
$this->view->allProjects = array(0 => '') + $this->project->getPairsByModel('all', 0, 'noclosed');
$this->view->executionID = $executionID;
$this->view->productID = $productID;
@@ -1589,6 +1629,7 @@ class execution extends control
/* If the story of the product which linked the execution, you don't allow to remove the product. */
$unmodifiableProducts = array();
$unmodifiableBranches = array();
$linkedStoryIDList = array();
foreach($linkedProducts as $productID => $linkedProduct)
{
if(!isset($allProducts[$productID])) $allProducts[$productID] = $linkedProduct->name;
@@ -1602,6 +1643,7 @@ class execution extends control
{
array_push($unmodifiableProducts, $productID);
array_push($unmodifiableBranches, $branchID);
$linkedStoryIDList[$productID][$branchID] = $executionStories[$productID][$branchID]->storyIDList;
}
}
}
@@ -1635,6 +1677,7 @@ class execution extends control
$this->view->allProducts = $allProducts;
$this->view->linkedProducts = $linkedProducts;
$this->view->linkedBranches = $linkedBranches;
$this->view->linkedStoryIDList = $linkedStoryIDList;
$this->view->branches = $branches;
$this->view->unmodifiableProducts = $unmodifiableProducts;
$this->view->unmodifiableBranches = $unmodifiableBranches;
@@ -2207,9 +2250,10 @@ class execution extends control
}
$userList = $this->dao->select('account, realname, avatar')->from(TABLE_USER)->where('deleted')->eq(0)->fetchAll('account');
$userList['closed']['account'] = 'Closed';
$userList['closed']['realname'] = 'Closed';
$userList['closed']['avatar'] = '';
$userList['closed'] = new stdclass();
$userList['closed']->account = 'Closed';
$userList['closed']->realname = 'Closed';
$userList['closed']->avatar = '';
$this->view->title = $this->lang->execution->kanban;
$this->view->realnames = $this->loadModel('user')->getPairs('noletter');
@@ -2618,6 +2662,7 @@ class execution extends control
/* If the story of the product which linked the execution, you don't allow to remove the product. */
$unmodifiableProducts = array();
$unmodifiableBranches = array();
$linkedStoryIDList = array();
foreach($linkedProducts as $productID => $linkedProduct)
{
$linkedBranches[$productID] = array();
@@ -2629,6 +2674,7 @@ class execution extends control
{
array_push($unmodifiableProducts, $productID);
array_push($unmodifiableBranches, $branchID);
$linkedStoryIDList[$productID][$branchID] = $executionStories[$productID][$branchID]->storyIDList;
}
}
}
@@ -2642,6 +2688,7 @@ class execution extends control
$this->view->unmodifiableProducts = $unmodifiableProducts;
$this->view->unmodifiableBranches = $unmodifiableBranches;
$this->view->linkedBranches = $linkedBranches;
$this->view->linkedStoryIDList = $linkedStoryIDList;
$this->view->branchGroups = $this->execution->getBranchByProduct(array_keys($allProducts), $this->config->systemMode == 'new' ? $execution->project : 0, 'ignoreNormal|noclosed');
$this->view->allBranches = $this->execution->getBranchByProduct(array_keys($allProducts), $this->config->systemMode == 'new' ? $execution->project : 0, 'ignoreNormal');
@@ -3115,8 +3162,12 @@ class execution extends control
*/
public function tips($executionID)
{
$this->view->execution = $this->execution->getById($executionID);
$execution = $this->execution->getById($executionID);
$projectID = $execution->project;
$this->view->execution = $execution;
$this->view->executionID = $executionID;
$this->view->projectID = $projectID;
$this->display('execution', 'tips');
}
@@ -3158,7 +3209,7 @@ class execution extends control
foreach($executions as $execution)
{
if(isset($orderedExecutions[$execution->parent])) unset($orderedExecutions[$execution->parent]);
if(isset($orderedExecutions[$execution->parent]) and $project->model != 'waterfall') unset($orderedExecutions[$execution->parent]);
$execution->teams = zget($teams, $execution->id, array());
$orderedExecutions[$execution->id] = $execution;
}
@@ -3180,11 +3231,20 @@ class execution extends control
$projectExecutions = array();
$parentIdList = array();
$childrenIdList = array();
foreach($orderedExecutions as $execution)
{
$projectExecutions[$execution->project][] = $execution;
if($execution->type != 'stage') continue;
if($execution->grade == 2 and $execution->project != $execution->parent) $parentIdList[$execution->parent] = $execution->parent;
if($execution->grade == 2 and $execution->project != $execution->parent)
{
$parentIdList[$execution->parent] = $execution->parent;
$childrenIdList[$execution->parent][] = $execution->id;
}
}
foreach($orderedExecutions as $id => $execution)
{
$execution->children = isset($childrenIdList[$execution->id]) ? $childrenIdList[$execution->id] : array();
$projectExecutions[$execution->project][] = $execution;
}
$parents = array();
@@ -3235,6 +3295,7 @@ class execution extends control
$orderBy = $this->post->orderBy;
$order = $this->dao->select('*')->from(TABLE_PROJECTSTORY)->where('story')->in($idList)->andWhere('project')->eq($executionID)->orderBy('order_asc')->fetch('order');
if(strpos($orderBy, 'order_desc') !== false) $idList = array_reverse($idList);
foreach($idList as $storyID)
{
$this->dao->update(TABLE_PROJECTSTORY)->set('`order`')->eq($order)->where('story')->eq($storyID)->andWhere('project')->eq($executionID)->exec();
+2 -1
View File
@@ -1,4 +1,5 @@
#tipsModal {margin-top: 10%;}
[lang^='en'] #tipsModal {margin-top: 10%; max-width: 620px;}
[lang^='zh-cn'] #tipsModal {margin-top: 10%; max-width: 530px;}
.chosen-container-single .chosen-single > span {max-width: 100%;}
#copyProjectModal {padding: 0;}
+1 -1
View File
@@ -1,2 +1,2 @@
#fromproject_chosen .chosen-single {width: 220px;}
.c-name {width: 150px;}
.c-name {width: 200px;}
+4
View File
@@ -0,0 +1,4 @@
$(function()
{
if($('#bugList thead th.c-title').width() < 150) $('#bugList thead th.c-title').width(150);
});
+1 -1
View File
@@ -73,7 +73,7 @@ $(function()
});
})
if(copyExecutionID != 0) $('#teams').change();
if(copyExecutionID != 0 || projectID != 0) $('#teams').change();
var acl = $("[name^='acl']:checked").val();
setWhite(acl);
+7 -3
View File
@@ -45,7 +45,9 @@ $(function()
if(isExistedProduct != -1 && productType == 'normal')
{
$(this).prop('disabled', true).trigger("chosen:updated");
$(this).siblings('div').find('span').attr('title', tip);
var productTip = tip.replace('%s', linkedStoryIDList[$(this).attr('data-last')][0]);
$(this).siblings('div').find('span').attr('title', productTip);
}
});
@@ -55,11 +57,13 @@ $(function()
if(isExistedBranch != -1)
{
var $product = $(this).closest('.has-branch').find("[name^='products']");
if($.inArray($product.val(), unmodifiableProducts) != -1)
if($.inArray($product.val(), unmodifiableProducts) != -1 && linkedStoryIDList[$product.val()][$(this).attr('data-last')])
{
$(this).prop('disabled', true).trigger("chosen:updated");
$product.prop('disabled', true).trigger("chosen:updated");
$product.siblings('div').find('span').attr('title', tip);
var productTip = tip.replace('%s', linkedStoryIDList[$product.val()][$(this).attr('data-last')]);
$product.siblings('div').find('span').attr('title', productTip);
}
}
});
+4 -3
View File
@@ -169,9 +169,10 @@ $(function()
$('#kanban').kanban(
{
data: processKanbanData(),
laneNameWidth: 5,
virtualize: true,
data: processKanbanData(),
laneNameWidth: 5,
virtualize: true,
virtualCardList: true,
droppable:
{
selector: '.kanban-item:not(.kanban-item-span)',
+12 -8
View File
@@ -348,7 +348,7 @@ if(!window.kanbanDropRules)
'wait': ['wait', 'developing', 'developed', 'canceled'],
'developing': ['developing', 'developed', 'pause', 'canceled'],
'developed': ['developed', 'developing', 'closed'],
'pause': ['pause', 'developing', 'developed', 'canceled'],
'pause': ['pause', 'developing', 'canceled'],
'canceled': ['canceled', 'developing', 'closed'],
'closed': ['closed', 'developing'],
}
@@ -430,9 +430,10 @@ function renderUserAvatar(user, objectType, objectID, size)
/**
* Render deadline
* @param {String|Date} deadline Deadline
* @param {string} status
* @returns {JQuery}
*/
function renderDeadline(deadline)
function renderDeadline(deadline, status)
{
if(deadline == '0000-00-00') return;
@@ -444,8 +445,10 @@ function renderDeadline(deadline)
now.setMilliseconds(0);
var isEarlyThanToday = date.getTime() < now.getTime();
var deadlineDate = $.zui.formatDate(date, 'MM-dd');
var statusList = ['wait','doing','pause'];
var textColor = isEarlyThanToday && typeof(status) != 'undefined' && statusList.indexOf(status) != -1 ? 'text-red' : 'text-muted';
return $('<span class="info info-deadline"/>').text(deadlineLang + ' ' + deadlineDate).addClass(isEarlyThanToday ? 'text-red' : 'text-muted');
return $('<span class="info info-deadline"/>').text(deadlineLang + ' ' + deadlineDate).addClass(textColor);
}
/**
@@ -600,15 +603,14 @@ function renderTaskItem(item, $item, col)
if(scaleSize <= 2)
{
var idHtml = scaleSize <= 1 ? ('<span class="info info-id text-muted">#' + item.id + '</span>') : '';
var priHtml = '<span class="info info-pri label-pri label-pri-' + item.pri + '" title="' + item.pri + '">' + item.pri + '</span>';
var hoursHtml = (item.estimate && scaleSize <= 1) ? ('<span class="info info-estimate text-muted">' + item.estimate + 'h</span>') : '';
var hoursHtml = scaleSize <= 1 && item.status != 'wait' ? ('<span class="info info-estimate text-muted">' + taskLang.leftAB + ' ' + item.left + 'h</span>') : ('<span class="info info-estimate text-muted">' + taskLang.estimateAB + ' ' + item.estimate + 'h</span>');
var avatarHtml = renderUserAvatar(item.assignedTo, 'task', item.id);
var $infos = $item.find('.infos');
if(!$infos.length) $infos = $('<div class="infos"></div>');
$infos.html([idHtml, priHtml, hoursHtml].join(''));
if(item.deadline && scaleSize <= 1) $infos.append(renderDeadline(item.deadline));
$infos.html([priHtml, hoursHtml].join(''));
if(item.deadline && scaleSize <= 1) $infos.append(renderDeadline(item.deadline, item.status));
$infos[scaleSize <= 1 ? 'append' : 'prepend'](avatarHtml);
if(scaleSize <= 1) $infos.appendTo($item);
@@ -1213,7 +1215,7 @@ function initKanban($kanban)
calcColHeight: calcColHeight,
minColWidth: 240,
maxColWidth: 240,
cardHeight: 60,
cardHeight: getCardHeight(),
fluidBoardWidth: fluidBoard,
displayCards: displayCards,
createColumnText: kanbanLang.createColumn,
@@ -1224,6 +1226,8 @@ function initKanban($kanban)
onRenderHeaderCol: renderHeaderCol,
onRenderCount: renderCount,
droppable: groupBy == 'default' ? {target: findDropColumns, finish:handleFinishDrop} : false,
virtualize: true,
virtualCardList: true
});
$kanban.on('click', '.action-cancel', hideKanbanAction);
+2 -2
View File
@@ -9,8 +9,8 @@ $(function()
var $target = $(data.element[0]);
$target.hide();
$target.fadeIn(1000);
order = 'order_asc'
history.pushState({}, 0, createLink('project', 'story', "executionID=" + executionID + '&orderBy=' + order));
order = 'order_desc'
history.pushState({}, 0, createLink('execution', 'story', "executionID=" + executionID + '&orderBy=' + order));
});
});
+9 -6
View File
@@ -45,9 +45,10 @@ function renderUserAvatar(user, objectType, objectID, size)
/**
* Render deadline
* @param {String|Date} deadline Deadline
@param {string} status
* @returns {JQuery}
*/
function renderDeadline(deadline)
function renderDeadline(deadline, status)
{
if(deadline == '0000-00-00') return;
@@ -59,8 +60,10 @@ function renderDeadline(deadline)
now.setMilliseconds(0);
var isEarlyThanToday = date.getTime() < now.getTime();
var deadlineDate = $.zui.formatDate(date, 'MM-dd');
var statusList = ['wait','doing','pause'];
var textColor = isEarlyThanToday && typeof(status) != 'undefined' && statusList.indexOf(status) != -1 ? 'text-red' : 'text-muted';
return $('<span class="info info-deadline"/>').text(deadlineLang + ' ' + deadlineDate).addClass(isEarlyThanToday ? 'text-red' : 'text-muted');
return $('<span class="info info-deadline"/>').text(deadlineLang + ' ' + deadlineDate).addClass(textColor);
}
/**
@@ -215,15 +218,14 @@ function renderTaskItem(item, $item, col)
if(scaleSize <= 2)
{
var idHtml = scaleSize <= 1 ? ('<span class="info info-id text-muted">#' + item.id + '</span>') : '';
var priHtml = '<span class="info info-pri label-pri label-pri-' + item.pri + '" title="' + item.pri + '">' + item.pri + '</span>';
var hoursHtml = (item.estimate && scaleSize <= 1) ? ('<span class="info info-estimate text-muted">' + item.estimate + 'h</span>') : '';
var hoursHtml = scaleSize <= 1 && item.status != 'wait' ? ('<span class="info info-estimate text-muted">' + taskLang.leftAB + ' ' + item.left + 'h</span>') : ('<span class="info info-estimate text-muted">' + taskLang.estimateAB + ' ' + item.estimate + 'h</span>');
var avatarHtml = renderUserAvatar(item.assignedTo, 'task', item.id);
var $infos = $item.find('.infos');
if(!$infos.length) $infos = $('<div class="infos"></div>');
$infos.html([idHtml, priHtml, hoursHtml].join(''));
if(item.deadline && scaleSize <= 1) $infos.append(renderDeadline(item.deadline));
$infos.html([priHtml, hoursHtml].join(''));
if(item.deadline && scaleSize <= 1) $infos.append(renderDeadline(item.deadline, item.status));
$infos[scaleSize <= 1 ? 'append' : 'prepend'](avatarHtml);
if(scaleSize <= 1) $infos.appendTo($item);
@@ -1055,6 +1057,7 @@ $(function()
virtualize: true,
onAction: handleKanbanAction,
virtualRenderOptions: {container: '#kanbanContainer>.panel-body'},
virtualCardList: true,
droppable:
{
target: findDropColumns,
+5 -2
View File
@@ -281,6 +281,7 @@ $lang->execution->RDKanban = 'Research & Development Kanban';
$lang->execution->allTasks = 'All';
$lang->execution->assignedToMe = 'My';
$lang->execution->myInvolved = 'Involved';
$lang->execution->assignedByMe = 'AssignedByMe';
$lang->execution->statusSelects[''] = 'More';
$lang->execution->statusSelects['wait'] = 'Waiting';
@@ -364,7 +365,7 @@ $lang->execution->confirmUnlinkStory = "After {$lang->SRCommon} is remo
$lang->execution->confirmSync = "After modifying the project, in order to maintain the consistency of data, the data of products, requirements, teams and whitelist associated with the implementation will be synchronized to the new project. Please know.";
$lang->execution->confirmUnlinkExecutionStory = "Do you want to unlink this Story from the execution?";
$lang->execution->notAllowedUnlinkStory = "This {$lang->SRCommon} is linked to the {$lang->executionCommon} of the execution. Remove it from the {$lang->executionCommon}, then try again.";
$lang->execution->notAllowRemoveProducts = "The story of this product is linked with the {$lang->executionCommon}. Unlink it before doing any action.";
$lang->execution->notAllowRemoveProducts = "The story %s of this product is linked with the {$lang->executionCommon}. Unlink it before doing any action.";
$lang->execution->errorNoLinkedProducts = "No {$lang->productCommon} is linked to {$lang->executionCommon}. You will be directed to {$lang->productCommon} page to link one.";
$lang->execution->errorSameProducts = "{$lang->executionCommon} cannot be linked to the same {$lang->productCommon} twice.";
$lang->execution->errorSameBranches = "{$lang->executionCommon} cannot be linked to the same branch twice";
@@ -378,7 +379,8 @@ $lang->execution->afterInfo = "{$lang->executionCommon} is cre
$lang->execution->setTeam = 'Set Team';
$lang->execution->linkStory = 'Link Story';
$lang->execution->createTask = 'Create Task';
$lang->execution->goback = "Go Back";
$lang->execution->goback = "Go Back Task List";
$lang->execution->gobackExecution = "Go Back Executioni List";
$lang->execution->noweekend = 'Exclude Weekend';
$lang->execution->nodelay = 'Exclude Delay Date';
$lang->execution->withweekend = 'Include Weekend';
@@ -464,6 +466,7 @@ $lang->execution->featureBar['task']['all'] = $lang->execution->allTask
$lang->execution->featureBar['task']['unclosed'] = $lang->execution->unclosed;
$lang->execution->featureBar['task']['assignedtome'] = $lang->execution->assignedToMe;
$lang->execution->featureBar['task']['myinvolved'] = $lang->execution->myInvolved;
$lang->execution->featureBar['task']['assignedbyme'] = $lang->execution->assignedByMe;
$lang->execution->featureBar['task']['delayed'] = 'Delayed';
$lang->execution->featureBar['task']['needconfirm'] = 'Changed';
$lang->execution->featureBar['task']['status'] = $lang->execution->statusSelects[''];
+4 -1
View File
@@ -281,6 +281,7 @@ $lang->execution->RDKanban = '研发看板';
$lang->execution->allTasks = '所有';
$lang->execution->assignedToMe = '指派给我';
$lang->execution->myInvolved = '由我参与';
$lang->execution->assignedByMe = '由我指派';
$lang->execution->statusSelects[''] = '更多';
$lang->execution->statusSelects['wait'] = '未开始';
@@ -364,7 +365,7 @@ $lang->execution->confirmUnlinkStory = "移除该{$lang->SRCommon}后
$lang->execution->confirmSync = "修改所属项目后,为了保持数据的一致性,该执行所关联的产品、需求、团队和白名单数据将会同步到新的项目中,请知悉。";
$lang->execution->confirmUnlinkExecutionStory = "您确定从该项目中移除该{$lang->SRCommon}吗?";
$lang->execution->notAllowedUnlinkStory = "该{$lang->SRCommon}已经与项目下{$lang->executionCommon}相关联,请从{$lang->executionCommon}中移除后再操作。";
$lang->execution->notAllowRemoveProducts = "该{$lang->productCommon}中的{$lang->SRCommon}已与该{$lang->executionCommon}进行了关联,请取消关联后再操作。";
$lang->execution->notAllowRemoveProducts = "该{$lang->productCommon}中的{$lang->SRCommon}%s已与该{$lang->executionCommon}进行了关联,请取消关联后再操作。";
$lang->execution->errorNoLinkedProducts = "该{$lang->executionCommon}没有关联的{$lang->productCommon},系统将转到{$lang->productCommon}关联页面";
$lang->execution->errorSameProducts = "{$lang->executionCommon}不能关联多个相同的{$lang->productCommon}。";
$lang->execution->errorSameBranches = "{$lang->executionCommon}不能关联多个相同的分支。";
@@ -379,6 +380,7 @@ $lang->execution->setTeam = '设置团队';
$lang->execution->linkStory = "关联{$lang->SRCommon}";
$lang->execution->createTask = '创建任务';
$lang->execution->goback = "返回任务列表";
$lang->execution->gobackExecution = "返回迭代列表";
$lang->execution->noweekend = '去除周末';
$lang->execution->nodelay = '去除延期日期';
$lang->execution->withweekend = '显示周末';
@@ -464,6 +466,7 @@ $lang->execution->featureBar['task']['all'] = $lang->execution->allTask
$lang->execution->featureBar['task']['unclosed'] = $lang->execution->unclosed;
$lang->execution->featureBar['task']['assignedtome'] = $lang->execution->assignedToMe;
$lang->execution->featureBar['task']['myinvolved'] = $lang->execution->myInvolved;
$lang->execution->featureBar['task']['assignedbyme'] = $lang->execution->assignedByMe;
$lang->execution->featureBar['task']['delayed'] = '已延期';
$lang->execution->featureBar['task']['needconfirm'] = "{$lang->SRCommon}变更";
$lang->execution->featureBar['task']['status'] = $lang->execution->statusSelects[''];
+49 -28
View File
@@ -369,12 +369,12 @@ class executionModel extends model
$this->lang->project->code = $this->lang->execution->execCode;
}
$sprintProject = isset($sprint->project) ? $sprint->project : '';
$sprintProject = isset($sprint->project) ? $sprint->project : '0';
$this->dao->insert(TABLE_EXECUTION)->data($sprint)
->autoCheck($skipFields = 'begin,end')
->batchcheck($this->config->execution->create->requiredFields, 'notempty')
->checkIF((!empty($sprint->name) and $this->config->systemMode == 'new'), 'name', 'unique', "`type` in ('sprint','stage', 'kanban') and `project` = $sprintProject")
->checkIF(!empty($sprint->code), 'code', 'unique', "`type` in ('sprint','stage')")
->checkIF(!empty($sprint->code), 'code', 'unique', "`type` in ('sprint','stage', 'kanban')")
->checkIF($sprint->begin != '', 'begin', 'date')
->checkIF($sprint->end != '', 'end', 'date')
->checkIF($sprint->end != '', 'end', 'ge', $sprint->begin)
@@ -511,18 +511,17 @@ class executionModel extends model
}
/* Update data. */
$executionProject = isset($execution->project) ? $execution->project : '';
$executionProject = isset($execution->project) ? $execution->project : '0';
$this->dao->update(TABLE_EXECUTION)->data($execution)
->autoCheck($skipFields = 'begin,end')
->batchcheck($this->config->execution->edit->requiredFields, 'notempty')
->checkIF($execution->begin != '', 'begin', 'date')
->checkIF($execution->end != '', 'end', 'date')
->checkIF($execution->end != '', 'end', 'ge', $execution->begin)
->checkIF((!empty($execution->name) and $this->config->systemMode == 'new'), 'name', 'unique', "id != $executionID and type in ('sprint','stage') and `project` = $executionProject")
->checkIF(!empty($execution->code), 'code', 'unique', "id != $executionID and type in ('sprint','stage')")
->checkIF((!empty($execution->name) and $this->config->systemMode == 'new'), 'name', 'unique', "id != $executionID and type in ('sprint','stage', 'kanban') and `project` = $executionProject")
->checkIF(!empty($execution->code), 'code', 'unique', "id != $executionID and type in ('sprint','stage', 'kanban')")
->checkFlow()
->where('id')->eq($executionID)
->checkFlow()
->limit(1)
->exec();
@@ -961,6 +960,16 @@ class executionModel extends model
}
}
/* Update the status of the parent stage. */
if($oldExecution->type == 'stage')
{
$parent = $this->getByID($oldExecution->parent);
if($parent->type == 'stage' and $parent->status == 'closed')
{
$this->dao->update(TABLE_EXECUTION)->set('status')->eq('doing')->where('id')->eq($parent->id)->exec();
}
}
if(!dao::isError()) return common::createChanges($oldExecution, $execution);
}
@@ -1002,6 +1011,23 @@ class executionModel extends model
if(!dao::isError())
{
/* Update the status of the parent stage. */
if($oldExecution->type == 'stage')
{
$parent = $this->getByID($oldExecution->parent);
if($parent->type == 'stage')
{
$isClosed = true;
$children = $this->getChildExecutions($oldExecution->parent);
foreach($children as $childID => $childExecution)
{
if($childExecution->status != 'closed') $isClosed = false;
}
if($isClosed) $this->dao->update(TABLE_EXECUTION)->set('status')->eq('closed')->where('id')->eq($parent->id)->exec();
}
}
$this->loadModel('score')->create('execution', 'close', $oldExecution);
return common::createChanges($oldExecution, $execution);
}
@@ -1575,7 +1601,7 @@ class executionModel extends model
*/
public function getChildExecutions($executionID)
{
return $this->dao->select('id, name')->from(TABLE_EXECUTION)->where('parent')->eq((int)$executionID)->fetchPairs();
return $this->dao->select('id, name, status')->from(TABLE_EXECUTION)->where('deleted')->eq(0)->andWhere('parent')->eq((int)$executionID)->fetchAll('id');
}
/**
@@ -1929,18 +1955,14 @@ class executionModel extends model
*/
public function getToImport($executionIds, $type)
{
$executions = $this->dao->select('*')->from(TABLE_EXECUTION)
->where('id')->in($executionIds)
->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->sprints)->fi()
->andWhere('type')->eq($type)
->andWhere('deleted')->eq(0)
->orderBy('id desc')
->fetchAll('id');
$pairs = array();
$now = date('Y-m-d');
foreach($executions as $id => $execution) $pairs[$id] = $execution->name;
return $pairs;
return $this->dao->select('t1.id,concat_ws(" / ", t2.name, t1.name) as name')->from(TABLE_EXECUTION)->alias('t1')
->leftJoin(TABLE_PROJECT)->alias('t2')->on('t2.id=t1.project')
->where('t1.id')->in($executionIds)
->beginIF(!$this->app->user->admin)->andWhere('t1.id')->in($this->app->user->view->sprints)->fi()
->andWhere('t1.type')->eq($type)
->andWhere('t1.deleted')->eq(0)
->orderBy('t1.id desc')
->fetchPairs('id', 'name');
}
/**
@@ -2008,17 +2030,15 @@ class executionModel extends model
*/
public function getTasks2Imported($toExecution, $branches)
{
$products = $this->loadModel('product')->getProducts($toExecution);
if(empty($products)) return array();
$execution = $this->getById($toExecution);
$project = $this->loadModel('project')->getById($execution->project);
$brotherProjects = $this->project->getBrotherProjects($project);
$executions = $this->dao->select('id')->from(TABLE_EXECUTION)
->where('project')->in($brotherProjects)
->andWhere('status')->ne('closed')
->fetchPairs('id');
$execution = $this->getById($toExecution);
$executions = $this->dao->select('t1.product, t1.project')->from(TABLE_PROJECTPRODUCT)->alias('t1')
->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.project=t2.id')
->where('t1.product')->in(array_keys($products))
->andWhere('t2.project')->eq($execution->project)
->fetchGroup('project');
$branches = str_replace(',', "','", $branches);
$tasks = $this->dao->select('t1.*, t2.id AS storyID, t2.title AS storyTitle, t2.version AS latestStoryVersion, t2.status AS storyStatus, t3.realname AS assignedToRealName')->from(TABLE_TASK)->alias('t1')
->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id')
->leftJoin(TABLE_USER)->alias('t3')->on('t1.assignedTo = t3.account')
@@ -2705,6 +2725,7 @@ class executionModel extends model
$objectPairs = $this->dao->select('id,name')->from(TABLE_PROJECT)
->where('deleted')->eq(0)
->andWhere('type')->ne('project')
->andWhere('(project')->eq($projectID)
->orWhere('id')->eq($projectID)
->markRight(1)
+46 -13
View File
@@ -26,6 +26,7 @@
#closed {width: 90px; height: 25px; line-height: 25px; background-color: #ddd; color: #3c495c; text-align: center; margin-left: 15px; border-radius: 2px;}
#gray-line {width: 230px;height: 1px; margin-left: 10px; margin-bottom:2px; background-color: #ddd;}
#dropMenu.has-search-text .hide-in-search {display: flex;}
#swapper li>.selected {color: #0c64eb!important; background: #e9f2fb!important;}
</style>
<?php
$executionCounts = array();
@@ -80,27 +81,59 @@ foreach($executions as $projectID => $projectExecutions)
if($execution->type == 'kanban' and $link != $kanbanLink) $link = $kanbanLink;
$selected = $execution->id == $executionID ? 'selected' : '';
if($execution->status != 'done' and $execution->status != 'closed' and ($execution->PM == $this->app->user->account or isset($execution->teams[$this->app->user->account])))
if(!empty($execution->children))
{
$myExecutionsHtml .= '<li>' . html::a(sprintf($link, $execution->id), $executionNames[$execution->id], '', "class='$selected clickable' title='{$execution->name}' data-key='" . zget($executionsPinYin, $execution->name, '') . "' data-app='{$this->app->tab}'") . '</li>';
foreach($execution->children as $id)
{
$selected = $id == $executionID ? 'selected' : '';
if($execution->status != 'done' and $execution->status != 'closed' and ($execution->PM == $this->app->user->account or isset($execution->teams[$this->app->user->account])))
{
$myExecutionsHtml .= '<li>' . html::a(sprintf($link, $id), $executionNames[$id], '', "class='$selected clickable' title='{$executionNames[$id]}' data-key='" . zget($executionsPinYin, $executionNames[$id], '') . "' data-app='{$this->app->tab}'") . '</li>';
if($selected == 'selected') $tabActive = 'myExecution';
if($selected == 'selected') $tabActive = 'myExecution';
$myExecutions ++;
$myExecutions ++;
}
else if($execution->status != 'done' and $execution->status != 'closed' and $execution->PM != $this->app->user->account and !isset($execution->teams[$this->app->user->account]))
{
$normalExecutionsHtml .= '<li>' . html::a(sprintf($link, $id), $executionNames[$id], '', "class='$selected clickable' title='{$executionNames[$id]}' data-key='" . zget($executionsPinYin, $executionNames[$id], '') . "' data-app='{$this->app->tab}'") . '</li>';
if($selected == 'selected') $tabActive = 'other';
$others ++;
}
else if($execution->status == 'done' or $execution->status == 'closed')
{
$closedExecutionsHtml .= '<li>' . html::a(sprintf($link, $id), $executionNames[$id], '', "class='$selected clickable' title='{$executionNames[$id]}' data-key='" . zget($executionsPinYin, $executionNames[$id], '') . "' data-app='{$this->app->tab}'") . '</li>';
if($selected == 'selected') $tabActive = 'closed';
}
}
}
else if($execution->status != 'done' and $execution->status != 'closed' and $execution->PM != $this->app->user->account and !isset($execution->teams[$this->app->user->account]))
else if($execution->grade == 1 or $config->systemMode == 'classic')
{
$normalExecutionsHtml .= '<li>' . html::a(sprintf($link, $execution->id), $executionNames[$execution->id], '', "class='$selected clickable' title='{$execution->name}' data-key='" . zget($executionsPinYin, $execution->name, '') . "' data-app='{$this->app->tab}'") . '</li>';
if($execution->status != 'done' and $execution->status != 'closed' and ($execution->PM == $this->app->user->account or isset($execution->teams[$this->app->user->account])))
{
$myExecutionsHtml .= '<li>' . html::a(sprintf($link, $execution->id), $executionNames[$execution->id], '', "class='$selected clickable' title='{$executionNames[$execution->id]}' data-key='" . zget($executionsPinYin, $execution->name, '') . "' data-app='{$this->app->tab}'") . '</li>';
if($selected == 'selected') $tabActive = 'other';
if($selected == 'selected') $tabActive = 'myExecution';
$others ++;
}
else if($execution->status == 'done' or $execution->status == 'closed')
{
$closedExecutionsHtml .= '<li>' . html::a(sprintf($link, $execution->id), $executionNames[$execution->id], '', "class='$selected clickable' title='$execution->name' data-key='" . zget($executionsPinYin, $execution->name, '') . "' data-app='{$this->app->tab}'") . '</li>';
$myExecutions ++;
}
else if($execution->status != 'done' and $execution->status != 'closed' and $execution->PM != $this->app->user->account and !isset($execution->teams[$this->app->user->account]))
{
$normalExecutionsHtml .= '<li>' . html::a(sprintf($link, $execution->id), $executionNames[$execution->id], '', "class='$selected clickable' title='{$executionNames[$execution->id]}' data-key='" . zget($executionsPinYin, $execution->name, '') . "' data-app='{$this->app->tab}'") . '</li>';
if($selected == 'selected') $tabActive = 'closed';
if($selected == 'selected') $tabActive = 'other';
$others ++;
}
else if($execution->status == 'done' or $execution->status == 'closed')
{
$closedExecutionsHtml .= '<li>' . html::a(sprintf($link, $execution->id), $executionNames[$execution->id], '', "class='$selected clickable' title='{$executionNames[$execution->id]}' data-key='" . zget($executionsPinYin, $execution->name, '') . "' data-app='{$this->app->tab}'") . '</li>';
if($selected == 'selected') $tabActive = 'closed';
}
}
/* If the execution is the last one in the project, print the closed label. */
+33 -9
View File
@@ -132,7 +132,7 @@
$onlyChildStage = ($execution->grade == 2 and $execution->project != $execution->parent);
if($onlyChildStage and isset($parents[$execution->parent])) $executionName = $parents[$execution->parent]->name . '/' . $executionName;
?>
<td class='text-left c-name <?php if(!empty($execution->children)) echo 'has-child';?> flex' title='<?php echo $executionName?>'>
<td class='text-left c-name sort-handler <?php if(!empty($execution->children)) echo 'has-child';?> flex' title='<?php echo $executionName?>'>
<?php if($config->systemMode == 'new'):?>
<span class='project-type-label label label-outline <?php echo $execution->type == 'stage' ? 'label-warning' : 'label-info';?>'><?php echo $lang->execution->typeList[$execution->type]?></span>
<?php endif;?>
@@ -185,12 +185,21 @@
echo common::hasPriv('programplan', 'create') ? html::a('javascript:alert("' . $this->lang->programplan->error->createdTask . '");', '<i class="icon-programplan-create icon-split"></i>', '', 'class="btn ' . $disabled . '"') : '';
}
common::printIcon('programplan', 'edit', "planID=$execution->id&projectID=$projectID", $execution, 'list', '', '', 'iframe', true);
common::printIcon('programplan', 'edit', "stageID=$execution->id&projectID=$projectID", $execution, 'list', '', '', 'iframe', true);
$disabled = !empty($execution->children) ? ' disabled' : '';
if($execution->status != 'closed' and common::hasPriv('execution', 'close', $execution))
{
common::printIcon('execution', 'close', "stageID=$execution->id", $execution, 'list', 'off', 'hiddenwin' , $disabled . ' iframe', true, '', $this->lang->programplan->close);
}
elseif($execution->status == 'closed' and common::hasPriv('execution', 'activate', $execution))
{
common::printIcon('execution', 'activate', "stageID=$execution->id", $execution, 'list', 'magic', 'hiddenwin' , $disabled . ' iframe', true, '', $this->lang->programplan->activate);
}
if(common::hasPriv('execution', 'delete', $execution))
{
common::printIcon('execution', 'delete', "planID=$execution->id&confirm=no", $execution, 'list', 'trash', 'hiddenwin' , $disabled, '', '', $this->lang->programplan->delete);
common::printIcon('execution', 'delete', "stageID=$execution->id&confirm=no", $execution, 'list', 'trash', 'hiddenwin' , $disabled, '', '', $this->lang->programplan->delete);
}
?></td>
<?php else:?>
@@ -228,6 +237,12 @@
if(isset($child->delay)) echo "<span class='label label-danger label-badge'>{$lang->execution->delayed}</span> ";
?>
</td>
<?php if($from == 'execution'): ?>
<td title = '<?php echo $child->code;?>'><?php echo $child->code;?>
<?php if($config->systemMode == 'new'):?>
<td title = '<?php echo $child->projectName?>'><?php echo $child->projectName;?>
<?php endif;?>
<?php endif;?>
<td><?php echo zget($users, $child->PM);?></td>
<?php $executionStatus = $this->processStatus('execution', $child);?>
<td class='c-status text-center' title='<?php echo $executionStatus;?>'>
@@ -236,7 +251,7 @@
<td class="c-progress">
<?php echo html::ring($child->hours->progress); ?>
</td>
<?php if($isStage):?>
<?php if($from == 'project' and $isStage):?>
<td><?php echo $child->percent . '%';?></td>
<td><?php echo zget($lang->stage->typeList, $child->attribute, '');?></td>
<td><?php echo helper::isZeroDate($child->begin) ? '' : $child->begin;?></td>
@@ -251,7 +266,7 @@
if($child->grade == 1 && $this->loadModel('programplan')->isCreateTask($child->id))
{
common::printIcon('programplan', 'create', "program={$child->parent}&productID=$productID&planID=$child->id", $child, 'list', 'split', '', '', '', '', $this->lang->programplan->createSubPlan);
common::printIcon('programplan', 'create', "program={$child->parent}&productID=$productID&stageID=$child->id", $child, 'list', 'split', '', '', '', '', $this->lang->programplan->createSubPlan);
}
else
{
@@ -259,21 +274,30 @@
echo html::a('javascript:alert("' . $this->lang->programplan->error->createdTask . '");', '<i class="icon-programplan-create icon-split"></i>', '', 'class="btn ' . $disabled . '"');
}
common::printIcon('programplan', 'edit', "planID=$child->id&projectID=$projectID", $child, 'list', '', '', 'iframe', true);
common::printIcon('programplan', 'edit', "stageID=$child->id&projectID=$projectID", $child, 'list', '', '', 'iframe', true);
$disabled = !empty($child->children) ? ' disabled' : '';
if(common::hasPriv('execution', 'close', $child) and $execution->status != 'closed')
{
common::printIcon('execution', 'close', "stageID=$child->id", $child, 'list', 'off', '' , $disabled . ' iframe', true, '', $this->lang->programplan->close);
}
elseif(common::hasPriv('execution', 'activate', $child) and $execution->status == 'closed')
{
common::printIcon('execution', 'activate', "stageID=$child->id", $child, 'list', 'magic', 'hiddenwin' , $disabled . ' iframe', true, '', $this->lang->programplan->activate);
}
if(common::hasPriv('execution', 'delete', $child))
{
common::printIcon('execution', 'delete', "planID=$child->id&confirm=no", $child, 'list', 'trash', 'hiddenwin' , $disabled, '', '', $this->lang->programplan->delete);
common::printIcon('execution', 'delete', "stageID=$child->id&confirm=no", $child, 'list', 'trash', 'hiddenwin' , $disabled, '', '', $this->lang->programplan->delete);
}
?>
</td>
<?php else:?>
<td class='c-begin' title='<?php echo helper::isZeroDate($child->begin) ? '' : $child->begin;?>'><?php echo helper::isZeroDate($child->begin) ? '' : $child->begin;?></td>
<td class='c-begin' title='<?php echo helper::isZeroDate($child->end) ? '' : $child->end;?>'><?php echo helper::isZeroDate($child->end) ? '' : $child->end;?></td>
<td class='hours' title='<?php echo $child->hours->totalEstimate . ' ' . $this->lang->execution->workHour;?>'><?php echo $child->hours->totalEstimate . ' ' . $this->lang->execution->workHourUnit;?></td>
<td class='hours' title='<?php echo $child->hours->totalConsumed . ' ' . $this->lang->execution->workHour;?>'><?php echo $child->hours->totalConsumed . ' ' . $this->lang->execution->workHourUnit;?></td>
<td class='hours' title='<?php echo $child->hours->totalLeft . ' ' . $this->lang->execution->workHour;?>'><?php echo $child->hours->totalLeft . ' ' . $this->lang->execution->workHourUnit;?></td>
<?php endif;?>
<?php if(!$isStage):?>
<td id='spark-<?php echo $child->id?>' class='sparkline text-left no-padding' values='<?php echo join(',', $child->burns);?>'></td>
<?php endif;?>
<?php foreach($extendFields as $extendField) echo "<td>" . $this->loadModel('flow')->getFieldValue($extendField, $child) . "</td>";?>
+59 -87
View File
@@ -11,6 +11,7 @@
*/
?>
<?php include '../../common/view/header.html.php';?>
<?php include '../../common/view/datatable.fix.html.php';?>
<style>
#subHeader #dropMenu .col-left .list-group {margin-bottom: 0px; padding-top: 10px;}
#subHeader #dropMenu .col-left {padding-bottom: 0px;}
@@ -53,113 +54,81 @@
</p>
</div>
<?php else:?>
<?php
$datatableId = $this->moduleName . ucfirst($this->methodName);
$useDatatable = (isset($config->datatable->$datatableId->mode) and $config->datatable->$datatableId->mode == 'datatable');
?>
<?php if($this->app->getViewType() == 'xhtml'):?>
<form class='main-table' method='post' id='executionBugForm'>
<?php else:?>
<form class='main-table' method='post' id='executionBugForm' data-ride="table">
<form class='main-table' method='post' id='executionBugForm' <?php if(!$useDatatable) echo "data-ride='table'";?>>
<?php endif;?>
<table class='table has-sort-head' id='bugList'>
<?php $canBatchAssignTo = common::hasPriv('bug', 'batchAssignTo');?>
<?php $vars = "executionID={$execution->id}&productID={$productID}&orderBy=%s&build=$buildID&type=$type&param=$param&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}"; ?>
<div class="table-header fixed-right">
<nav class="btn-toolbar pull-right setting"></nav>
</div>
<?php
$vars = "executionID={$execution->id}&productID={$productID}&orderBy=%s&build=$buildID&type=$type&param=$param&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}";
$useDatatable ? include '../../common/view/datatable.html.php' : include '../../common/view/tablesorter.html.php';
$setting = $this->datatable->getSetting('execution');
$widths = $this->datatable->setFixedFieldWidth($setting);
$columns = 0;
$canBatchAssignTo = common::hasPriv('bug', 'batchAssignTo');
?>
<?php if(!$useDatatable) echo '<div class="table-responsive">';?>
<table class='table has-sort-head<?php if($useDatatable) echo ' datatable';?>' id='bugList' data-fixed-left-width='<?php echo $widths['leftWidth']?>' data-fixed-right-width='<?php echo $widths['rightWidth']?>'>
<thead>
<tr>
<?php if($this->app->getViewType() == 'xhtml'):?>
<th class='c-id'>
<?php common::printOrderLink('id', $orderBy, $vars, $lang->idAB);?>
</th>
<th class='c-pri'><?php common::printOrderLink('pri', $orderBy, $vars, $lang->priAB);?></th>
<th><?php common::printOrderLink('title', $orderBy, $vars, $lang->bug->title);?></th>
<th class='c-status'><?php common::printOrderLink('status', $orderBy, $vars, $lang->bug->statusAB);?></th>
<?php
foreach($setting as $value)
{
if($value->id == 'title' || $value->id == 'id' || $value->id == 'pri' || $value->id == 'status')
{
$this->datatable->printHead($value, $orderBy, $vars, $canBatchAssignTo);
$columns ++;
}
}
?>
<?php else:?>
<th class='c-id'>
<?php if($canBatchAssignTo):?>
<div class="checkbox-primary check-all" title="<?php echo $lang->selectAll?>">
<label></label>
</div>
<?php endif;?>
<?php common::printOrderLink('id', $orderBy, $vars, $lang->idAB);?>
</th>
<th class='c-severity' title=<?php echo $lang->bug->severity;?>><?php common::printOrderLink('severity', $orderBy, $vars, $lang->bug->severityAB);?></th>
<th class='c-pri' title=<?php echo $lang->execution->pri;?>><?php common::printOrderLink('pri', $orderBy, $vars, $lang->priAB);?></th>
<th><?php common::printOrderLink('title', $orderBy, $vars, $lang->bug->title);?></th>
<th class='c-user'><?php common::printOrderLink('openedBy', $orderBy, $vars, $lang->openedByAB);?></th>
<th class='c-date text-center'><?php common::printOrderLink('deadline', $orderBy, $vars, $lang->bug->deadlineAB);?></th>
<th class='c-user'><?php common::printOrderLink('assignedTo', $orderBy, $vars, $lang->assignedToAB);?></th>
<th class='c-user'><?php common::printOrderLink('resolvedBy', $orderBy, $vars, $lang->bug->resolvedBy);?></th>
<th class='c-resolution'><?php common::printOrderLink('resolution', $orderBy, $vars, $lang->bug->resolutionAB);?></th>
<th class='c-actions-5'><?php echo $lang->actions;?></th>
<?php
foreach($setting as $value)
{
if($value->show)
{
if(common::checkNotCN() and $value->id == 'severity') $value->name = $lang->bug->severity;
if(common::checkNotCN() and $value->id == 'pri') $value->name = $lang->bug->pri;
if(common::checkNotCN() and $value->id == 'confirmed') $value->name = $lang->bug->confirmed;
$this->datatable->printHead($value, $orderBy, $vars, $canBatchAssignTo);
$columns ++;
}
}
?>
<?php endif;?>
</tr>
</thead>
<?php
$hasCustomSeverity = false;
foreach($lang->bug->severityList as $severityKey => $severityValue)
{
if(!empty($severityKey) and (string)$severityKey != (string)$severityValue)
{
$hasCustomSeverity = true;
break;
}
}
?>
<tbody>
<?php foreach($bugs as $bug):?>
<?php
$canBeChanged = common::canBeChanged('bug', $bug);
$arrtibute = $canBeChanged ? '' : 'disabled';
$viewLink = helper::createLink('bug', 'view', "bugID=$bug->id");
?>
<tr>
<tr data-id='<?php echo $bug->id?>'>
<?php if($this->app->getViewType() == 'xhtml'):?>
<?php $status = $this->processStatus('bug', $bug);?>
<td class='cell-id'>
<?php printf('%03d', $bug->id);?>
</td>
<td><span class='label-pri <?php echo 'label-pri-' . $bug->pri?>' title='<?php echo zget($lang->bug->priList, $bug->pri, $bug->pri)?>'><?php echo zget($lang->bug->priList, $bug->pri, $bug->pri)?></span></td>
<td class='text-left' title="<?php echo $bug->title?>"><?php echo html::a($viewLink, $bug->title, null, "style='color: $bug->color' data-app={$this->app->tab}");?></td>
<td class='c-status' title='<?php echo $status;?>'>
<span class='status-bug status-<?php echo $bug->status;?>'><?php echo $status;?></span>
</td>
<?php else:?>
<td class='cell-id'>
<?php if($canBatchAssignTo):?>
<?php echo html::checkbox('bugIDList', array($bug->id => ''), '', $arrtibute) . html::a($viewLink, sprintf('%03d', $bug->id), '', "data-app={$this->app->tab}");?>
<?php else:?>
<?php printf('%03d', $bug->id);?>
<?php endif;?>
</td>
<td>
<?php if($hasCustomSeverity):?>
<span class='<?php echo 'label-severity-custom';?>' title='<?php echo zget($lang->bug->severityList, $bug->severity);?>' data-severity='<?php echo $bug->severity;?>'><?php echo zget($lang->bug->severityList, $bug->severity, $bug->severity);?></span>
<?php else:?>
<span class='<?php echo 'label-severity';?>' title='<?php echo zget($lang->bug->severityList, $bug->severity);?>' data-severity='<?php echo $bug->severity;?>'></span>
<?php endif;?>
</td>
<td><span class='label-pri <?php echo 'label-pri-' . $bug->pri?>' title='<?php echo zget($lang->bug->priList, $bug->pri, $bug->pri)?>'><?php echo zget($lang->bug->priList, $bug->pri, $bug->pri)?></span></td>
<td class='text-left text-ellipsis' title="<?php echo $bug->title?>"><?php echo html::a($viewLink, $bug->title, null, "style='color: $bug->color' data-app={$this->app->tab}");?></td>
<td><?php echo zget($users, $bug->openedBy, $bug->openedBy);?></td>
<td class="text-center <?php echo (isset($bug->delay) and $bug->status == 'active') ? 'delayed' : '';?>"><?php if(substr($bug->deadline, 0, 4) > 0) echo substr($bug->deadline, 5, 6);?></td>
<td class='c-assignedTo has-btn text-left'><?php $this->bug->printAssignedHtml($bug, $users);?></td>
<td><?php echo zget($users, $bug->resolvedBy, $bug->resolvedBy);?></td>
<td><?php echo zget($lang->bug->resolutionList, $bug->resolution);?></td>
<td class='c-actions'>
<?php
if($canBeChanged)
<?php
foreach($setting as $value)
{
$params = "bugID=$bug->id";
common::printIcon('bug', 'confirmBug', $params, $bug, 'list', 'ok', '', 'iframe', true);
common::printIcon('bug', 'resolve', $params, $bug, 'list', 'checked', '', 'iframe', true);
common::printIcon('bug', 'close', $params, $bug, 'list', '', '', 'iframe', true);
common::printIcon('bug', 'create', "product=$bug->product&branch=$bug->branch&extra=$params,executionID=$bug->execution", $bug, 'list', 'copy');
common::printIcon('bug', 'edit', $params, $bug, 'list');
}
?>
</td>
if($value->id == 'title' || $value->id == 'id' || $value->id == 'pri' || $value->id == 'status')
{
$this->bug->printCell($value, $bug, $users, $builds, $branchOption, $modulePairs, array($execution), $plans, $stories, $tasks, $useDatatable ? 'datatable' : 'table');
}
}?>
<?php else:?>
<?php foreach($setting as $value) $this->bug->printCell($value, $bug, $users, $builds, $branchOption, $modulePairs, $executions, $plans, $stories, $tasks, $useDatatable ? 'datatable' : 'table', $projectPairs);?>
<?php endif;?>
</tr>
<?php endforeach;?>
</tbody>
</table>
<?php if(!$useDatatable) echo '</div>';?>
<div class='table-footer'>
<?php if($canBatchAssignTo):?>
<div class="checkbox-primary check-all"><label><?php echo $lang->selectAll?></label></div>
@@ -206,6 +175,9 @@
<?php js::set('replaceID', 'bugList');?>
<?php js::set('browseType', $type);?>
<script>
<?php if(!empty($useDatatable)):?>
$(function(){$('#executionBugForm').table();})
<?php endif;?>
function handleLinkButtonClick()
{
var xxcUrl = "xxc:openInApp/zentao-integrated/" + encodeURIComponent(window.location.href.replace(/.display=card/, '').replace(/\.xhtml/, '.html'));
+2 -2
View File
@@ -15,7 +15,7 @@
<?php $defaultURL = $this->createLink('execution', 'task', "execution=$executionID");?>
<?php include '../../common/view/header.html.php';?>
<body>
<div class='modal-dialog mw-500px' id='tipsModal'>
<div class='modal-dialog' id='tipsModal'>
<div class='modal-header'>
<a href='<?php echo $defaultURL;?>' class='close'><i class="icon icon-close"></i></a>
<h4 class='modal-title' id='myModalLabel'><?php echo $lang->execution->tips;?></h4>
@@ -183,7 +183,7 @@
</tr>
<tr>
<th><?php echo $lang->execution->copyTeam;?></th>
<td><?php echo html::select('teams', $teams, $copyExecutionID, "class='form-control chosen' data-placeholder='{$lang->execution->copyTeamTip}'"); ?></td>
<td><?php echo html::select('teams', $teams, empty($copyExecution) ? $projectID : $copyExecutionID, "class='form-control chosen' data-placeholder='{$lang->execution->copyTeamTip}'"); ?></td>
</tr>
<tr>
<th rowspan='2'><?php echo $lang->execution->owner;?></th>
+2 -1
View File
@@ -25,7 +25,7 @@
</div>
<form class='load-indicator main-form form-ajax' method='post' target='hiddenwin' id='dataform'>
<table class='table table-form'>
<?php if($config->systemMode == 'new'):?>
<?php if($config->systemMode == 'new' and isset($project) and $project->model == 'scrum'):?>
<tr>
<th class='w-120px'><?php echo $lang->execution->projectName;?></th>
<td><?php echo html::select('project', $allProjects, $execution->project, "class='form-control chosen' onchange='changeProject(this.value)' required");?></td><td></td>
@@ -218,6 +218,7 @@
<?php js::set('errorSameBranches', $lang->execution->errorSameBranches);?>
<?php js::set('unmodifiableProducts',$unmodifiableProducts);?>
<?php js::set('unmodifiableBranches', $unmodifiableBranches)?>
<?php js::set('linkedStoryIDList', $linkedStoryIDList)?>
<?php js::set('multiBranchProducts', $multiBranchProducts);?>
<?php js::set('tip', $lang->execution->notAllowRemoveProducts);?>
<?php js::set('confirmSync', $lang->execution->confirmSync);?>
+1 -1
View File
@@ -53,7 +53,7 @@
</div>
<?php printf('%03d', $task->id);?>
</td>
<td><?php echo $executions[$task->execution];?></td>
<td title="<?php echo $executions[$task->execution];?>"><?php echo $executions[$task->execution];?></td>
<td><span class='label-pri label-pri-<?php echo $task->pri;?>' title='<?php echo zget($lang->task->priList, $task->pri, $task->pri);?>'><?php echo $task->pri == '0' ? '' : zget($lang->task->priList, $task->pri, $task->pri);?></span></td>
<td class='text-left nobr'><?php if(!common::printLink('task', 'view', "task=$task->id", $task->name)) echo $task->name;?></td>
<td <?php echo $class;?>><?php echo $task->assignedToRealName;?></td>
@@ -28,9 +28,9 @@
<?php foreach($allProducts as $productID => $productName):?>
<?php if(isset($linkedProducts[$productID])):?>
<?php foreach($linkedBranches[$productID] as $branchID):?>
<?php if(($execution->grade < 2 and in_array($productID, $unmodifiableProducts) and in_array($branchID, $unmodifiableBranches))) $attr = "disabled='disabled'";?>
<?php if(($execution->grade < 2 and !(in_array($productID, $unmodifiableProducts) and in_array($branchID, $unmodifiableBranches)))) $attr = '';?>
<?php $title = (in_array($productID, $unmodifiableProducts) and in_array($branchID, $unmodifiableBranches)) ? $lang->execution->notAllowRemoveProducts : $productName;?>
<?php if(($execution->grade < 2 and in_array($productID, $unmodifiableProducts) and in_array($branchID, $unmodifiableBranches)) and !empty($linkedStoryIDList[$productID][$branchID])) $attr = "disabled='disabled'";?>
<?php if($execution->grade < 2 and (!(in_array($productID, $unmodifiableProducts) and in_array($branchID, $unmodifiableBranches)) or empty($linkedStoryIDList[$productID][$branchID]))) $attr = '';?>
<?php $title = (in_array($productID, $unmodifiableProducts) and in_array($branchID, $unmodifiableBranches) and !empty($linkedStoryIDList[$productID][$branchID])) ? sprintf($lang->execution->notAllowRemoveProducts, $linkedStoryIDList[$productID][$branchID]) : $productName;?>
<?php $checked = 'checked';?>
<div class='col-sm-4'>
<div class='product <?php echo $checked . (isset($allBranches[$productID]) ? ' has-branch' : '')?>'>
+2 -1
View File
@@ -1,9 +1,10 @@
<div style='margin: 0 auto; max-width: 400px'>
<div style='margin: 0 auto; max-width: <?php echo $this->app->getClientLang() == 'zh-cn' ? '500px' : '580px';?>'>
<p><strong><?php echo $lang->execution->afterInfo;?></strong></p>
<div>
<?php echo html::a($this->createLink('execution', 'team', "executionID=$executionID"), $lang->execution->setTeam, '', "class='btn' data-app='execution'");?>
<?php if($execution->lifetime != 'ops') echo html::a($this->createLink('execution', 'linkstory', "executionID=$executionID"), $lang->execution->linkStory, '', "class='btn' data-app='execution'");?>
<?php echo html::a($this->createLink('task', 'create', "execution=$executionID"), $lang->execution->createTask, '', "class='btn' data-app='execution'");?>
<?php echo html::a($this->createLink('execution', 'task', "executionID=$executionID"), $lang->execution->goback, '', "class='btn' data-app='execution'");?>
<?php echo html::a($this->createLink('project', 'execution', "status=all&projectID=$projectID"), $lang->execution->gobackExecution, '', "class='btn' data-app='project'");?>
</div>
</div>
+3 -2
View File
@@ -53,8 +53,9 @@
<script>
function setCharset(charset)
{
var param = (config.requestType == 'PATH_INFO' ? '?' : '&') + 'charset=' + charset;
var link = createLink('file', 'download', 'fileID=' + fileID + '&mouse=left') + param;
var link = createLink('file', 'download', 'fileID=' + fileID + '&mouse=left');
link += link.indexOf('?') >= 0 ? '&' : '?';
link += 'charset=' + charset;
location.href = link;
}
</script>
+1
View File
@@ -257,6 +257,7 @@ if($isCustomExport)
{
$field = trim($field);
$exportFieldPairs[$field] = isset($moduleLang->$field) ? $moduleLang->$field : (isset($lang->$field) ? $lang->$field : $field);
if(!is_string($exportFieldPairs[$field])) $exportFieldPairs[$field] = $field;
if(!$hasDefaultField)$selectedFields[] = $field;
}
js::set('defaultExportFields', join(',', $selectedFields));
+15 -7
View File
@@ -1,8 +1,5 @@
<?php if($files):?>
<?php
$sessionString = ($config->requestType == 'PATH_INFO' and !isonlybody()) ? '?' : '&';
$sessionString .= session_name() . '=' . session_id();
?>
<?php $sessionString = session_name() . '=' . session_id();?>
<?php if($fieldset == 'true'):?>
<div class="detail">
<div class="detail-title"><?php echo $lang->file->common;?> <i class="icon icon-paper-clip icon-sm"></i></div>
@@ -25,17 +22,24 @@ $sessionString .= session_name() . '=' . session_id();
{
if(!fileID) return;
var fileTypes = 'txt,jpg,jpeg,gif,png,bmp';
var sessionString = '<?php echo $sessionString;?>';
var windowWidth = $(window).width();
var url = createLink('file', 'download', 'fileID=' + fileID + '&mouse=left') + sessionString;
var width = (windowWidth > imageWidth) ? ((imageWidth < windowWidth * 0.5) ? windowWidth * 0.5 : imageWidth) : windowWidth;
var checkExtension = fileTitle.lastIndexOf('.' + extension) == (fileTitle.length - extension.length - 1);
var url = createLink('file', 'download', 'fileID=' + fileID + '&mouse=left');
url += url.indexOf('?') >= 0 ? '&' : '?';
url += '<?php echo $sessionString;?>';
if(fileTypes.indexOf(extension) >= 0 && checkExtension && config.onlybody != 'yes')
{
$('<a>').modalTrigger({url: url, type: 'iframe', width: width}).trigger('click');
}
else
{
url = url.replace('?onlybody=yes&', '?');
url = url.replace('?onlybody=yes', '?');
url = url.replace('&onlybody=yes', '');
window.open(url, '_blank');
}
return false;
@@ -78,7 +82,11 @@ $sessionString .= session_name() . '=' . session_id();
$file->size = round($file->size / (1024 * 1024 * 1024), 2);
$fileSize = $file->size . 'G';
}
echo "<li title='{$uploadDate}'>" . html::a($this->createLink('file', 'download', "fileID=$file->id") . $sessionString, $fileTitle . " <span class='text-muted'>({$fileSize})</span>", '_blank', "onclick=\"return downloadFile($file->id, '$file->extension', $imageWidth, '$file->title')\"");
$downloadLink = $this->createLink('file', 'download', "fileID=$file->id");
$downloadLink .= strpos($downloadLink, '?') === false ? '?' : '&';
$downloadLink .= $sessionString;
echo "<li title='{$uploadDate}'>" . html::a($downloadLink, $fileTitle . " <span class='text-muted'>({$fileSize})</span>", '_blank', "onclick=\"return downloadFile($file->id, '$file->extension', $imageWidth, '$file->title')\"");
$objectType = zget($this->config->file->objectType, $file->objectType);
if(common::hasPriv($objectType, 'edit', $object))
+3 -2
View File
@@ -690,12 +690,13 @@ class gitlabModel extends model
* @param string $simple
* @param int $minID
* @param int $maxID
* @param bool $sudo
* @access public
* @return array
*/
public function apiGetProjects($gitlabID, $simple = 'true', $minID = 0, $maxID = 0)
public function apiGetProjects($gitlabID, $simple = 'true', $minID = 0, $maxID = 0, $sudo = true)
{
$apiRoot = $this->getApiRoot($gitlabID);
$apiRoot = $this->getApiRoot($gitlabID, $sudo);
if(!$apiRoot) return array();
$url = sprintf($apiRoot, "/projects");
+1 -1
View File
@@ -22,7 +22,7 @@ $lang->group->managePrivByModule = 'Manage Privileges by Module';
$lang->group->byModuleTips = '<span class="tips">(Press Shift/Ctrl to Multi-select)</span>';
$lang->group->manageMember = 'Manage Members';
$lang->group->manageProjectAdmin = 'Manage Program Admins';
$lang->group->confirmDelete = 'Do you want to delete this user group?';
$lang->group->confirmDelete = "Do you want to delete '%s'?";
$lang->group->successSaved = 'Saved.';
$lang->group->errorNotSaved = 'Failed. Please select actions and groups.';
$lang->group->viewList = 'Access Sight';
+3
View File
@@ -624,6 +624,7 @@ $lang->resource->kanban->sortSpace = 'sortSpace';
$lang->resource->kanban->create = 'create';
$lang->resource->kanban->edit = 'edit';
$lang->resource->kanban->view = 'view';
$lang->resource->kanban->activate = 'activate';
$lang->resource->kanban->close = 'close';
$lang->resource->kanban->delete = 'delete';
$lang->resource->kanban->createRegion = 'createRegion';
@@ -712,6 +713,7 @@ $lang->kanban->methodorder[220] = 'setColumnWidth';
$lang->kanban->methodOrder[225] = 'batchCreateCard';
$lang->kanban->methodorder[230] = 'import';
$lang->kanban->methodorder[235] = 'enableArchived';
$lang->kanban->methodorder[240] = 'activate';
/* Execution. */
$lang->resource->execution = new stdclass();
@@ -1270,6 +1272,7 @@ $lang->resource->custom->editStoryConcept = 'editStoryConcept';
$lang->resource->custom->browseStoryConcept = 'browseStoryConcept';
$lang->resource->custom->setDefaultConcept = 'setDefaultConcept';
$lang->resource->custom->deleteStoryConcept = 'deleteStoryConcept';
$lang->resource->custom->kanban = 'kanban';
$lang->custom->methodOrder[5] = 'index';
$lang->custom->methodOrder[10] = 'set';
+1 -1
View File
@@ -22,7 +22,7 @@ $lang->group->managePrivByModule = '按模块分配权限';
$lang->group->byModuleTips = '<span class="tips">(可以按住Shift或者Ctrl键进行多选)</span>';
$lang->group->manageMember = '成员维护';
$lang->group->manageProjectAdmin = '维护项目管理员';
$lang->group->confirmDelete = '您确定删除该用户分组吗?';
$lang->group->confirmDelete = '您确定删除“%s”用户分组吗?';
$lang->group->successSaved = '成功保存';
$lang->group->errorNotSaved = '没有保存,请确认选择了权限数据。';
$lang->group->viewList = '可访问视图';
+2 -1
View File
@@ -65,7 +65,8 @@
else
{
$deleteURL = $this->createLink('group', 'delete', "groupID=$group->id&confirm=yes");
echo html::a("javascript:ajaxDelete(\"$deleteURL\", \"groupList\", confirmDelete)", '<i class="icon icon-trash"></i>', '', "title='{$lang->group->delete}' class='btn'");
js::set("confirmDelete{$group->id}", sprintf($lang->group->confirmDelete, $group->name));
echo html::a("javascript:ajaxDelete(\"$deleteURL\", \"groupList\", confirmDelete{$group->id})", '<i class="icon icon-trash"></i>', '', "title='{$lang->group->delete}' class='btn'");
}
}
?>
+10 -1
View File
@@ -33,6 +33,12 @@
{
if(item === 'divider') return $menuMainNav.append('<li class="divider"></li>');
/* Append tid param to app url */
if($.tabSession && item.url)
{
item.url = $.tabSession.convertUrlWithTid(item.url);
}
var $link= $('<a data-pos="menu"></a>')
.attr('data-app', item.code)
.attr('data-toggle', 'tooltip')
@@ -429,6 +435,8 @@
if(!app) return;
if(url === true) url = app.url;
else if($.tabSession) url = $.tabSession.convertUrlWithTid(url);
var iframe = app.$iframe[0];
/* Add hook to page before reload it */
@@ -720,7 +728,8 @@ $.extend(
var reg = /[^0-9]/;
if(reg.test(objectValue) || objectType == 'all')
{
var searchLink = createLink('search', 'index') + (config.requestType == 'PATH_INFO' ? '?' : '&') + 'words=' + objectValue;
var searchLink = createLink('search', 'index');
searchLink += (searchLink.indexOf('?') >= 0 ? '&' : '?') + 'words=' + objectValue;
$.apps.open(searchLink);
}
else
+1 -1
View File
@@ -18,7 +18,7 @@ js::set('vision', $config->vision);
js::set('navGroup', $lang->navGroup);
js::set('appsLang', $lang->index->app);
js::set('appsMenuItems', commonModel::getMainNavList($app->rawModule));
js::set('defaultOpen', $open);
js::set('defaultOpen', (isset($open) and !empty($open)) ? $open : '');
js::set('manualText', $lang->manual);
js::set('manualUrl', ((!empty($config->isINT)) ? $config->manualUrl['int'] : $config->manualUrl['home']) . '&theme=' . $_COOKIE['theme']);
js::set('showFeatures', $showFeatures);
+1
View File
@@ -40,6 +40,7 @@ $config->kanban->editor->createspace = array('id' => 'desc', 'tools' => 'simple
$config->kanban->editor->editspace = array('id' => 'desc', 'tools' => 'simpleTools');
$config->kanban->editor->closespace = array('id' => 'comment', 'tools' => 'simpleTools');
$config->kanban->editor->createcard = array('id' => 'desc', 'tools' => 'simpleTools');
$config->kanban->editor->activate = array('id' => 'comment', 'tools' => 'simpleTools');
$config->kanban->editor->close = array('id' => 'comment', 'tools' => 'simpleTools');
$config->kanban->editor->editcard = array('id' => 'desc', 'tools' => 'simpleTools');
$config->kanban->editor->viewcard = array('id' => 'comment', 'tools' => 'simpleTools');
+39 -6
View File
@@ -239,6 +239,36 @@ class kanban extends control
$this->display();
}
/*
* Activate a kanban.
*
* @param int $kanbanID
* @access public
* @return void
*/
public function activate($kanbanID)
{
$this->loadModel('action');
if(!empty($_POST))
{
$changes = $this->kanban->activate($kanbanID);
if(dao::isError()) return print(js::error(dao::getError()));
$actionID = $this->action->create('kanban', $kanbanID, 'activated', $this->post->comment);
$this->action->logHistory($actionID, $changes);
return print(js::reload('parent.parent'));
}
$this->view->kanban = $this->kanban->getByID($kanbanID);
$this->view->actions = $this->action->getList('kanban', $kanbanID);
$this->view->users = $this->loadModel('user')->getPairs('noletter');
$this->display();
}
/*
* Close a kanban.
*
@@ -741,6 +771,9 @@ class kanban extends control
}
}
$region = $this->kanban->getRegionByID($regionID);
$this->view->kanban = $this->kanban->getByID($region->kanban);
$this->view->columns = $columnsData;
$this->display();
@@ -975,6 +1008,9 @@ class kanban extends control
{
$this->kanban->moveCard($cardID, $fromColID, $toColID, $fromLaneID, $toLaneID, $kanbanID);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$this->loadModel('action')->create('kanbanCard', $cardID, 'moved');
$kanbanGroup = $this->kanban->getKanbanData($kanbanID);
echo json_encode($kanbanGroup);
}
@@ -1323,7 +1359,7 @@ class kanban extends control
$cards = $this->kanban->getCardsByObject('region', $regionID, 1);
foreach($this->config->kanban->fromType as $fromType)
{
$cards = $this->kanban->getImportedCards($region->kanban, $cards, $fromType, 1);
$cards = $this->kanban->getImportedCards($region->kanban, $cards, $fromType, 1, $regionID);
}
$this->view->kanban = $this->kanban->getByID($region->kanban);
@@ -1389,11 +1425,8 @@ class kanban extends control
else
{
if($card->fromType == '') $this->kanban->delete(TABLE_KANBANCARD, $cardID);
if($card->fromType != '')
{
$this->dao->delete()->from(TABLE_KANBANCARD)->where('id')->eq($cardID)->exec();
$this->loadModel('action')->create('kanbancard', $cardID, 'Deleted', '', $cardID);
}
if($card->fromType != '') $this->dao->delete()->from(TABLE_KANBANCARD)->where('id')->eq($cardID)->exec();
if(isonlybody()) return print(js::reload('parent.parent'));
return print(js::reload('parent'));
+1
View File
@@ -127,6 +127,7 @@
.kanban-card .releaseTitle .icon, .kanban-card .title .icon, .kanban-card .productplanTitle .icon, .kanban-card .title .icon, .kanban-card .buildTitle .icon{padding-right: 5px;}
.kanban-card .label-finish {margin-right: 7px; margin-top: -1px; padding: 3px 5px; float: left; background-color:#2a5f29;}
.kanban-card .releaseTitle, .kanban-card .productplanTitle {width: 100%; float: left;}
.kanban-card .productplanDesc {min-width: 200px;}
.progress-box {width:100%; display: flex; flex-direction: row; margin-top: 10px;}
.progress {flex: auto; margin: 5px auto 5px;}
+23 -8
View File
@@ -40,6 +40,7 @@ function fullScreen()
$('.action').hide();
$('.kanban-group-header').hide();
$(".title").attr("disabled", true).css("pointer-events", "none");
$('.kanban-col.kanban-header-col').css('padding', '0px 0px 0px 0px');
window.sortableDisabled = true;
$.cookie('isFullScreen', 1);
};
@@ -76,12 +77,15 @@ function fullScreen()
*/
function exitFullScreen()
{
$('.region-actions > div > .action').show();
$(".title").attr("disabled", false).css("pointer-events", "auto");
if(!CRKanban && kanban.status == 'closed') return;
$('#kanbanContainer').removeClass('fullscreen')
.off('scroll', tryUpdateKanbanAffix);
$('.actions').show();
$('.action').show();
$('.kanban-group-header').show();
$(".title").attr("disabled", false).css("pointer-events", "auto");
$('.kanban-col.kanban-header-col').css('padding', '0px 30px');
window.sortableDisabled = false;
$.cookie('isFullScreen', 0);
}
@@ -147,7 +151,7 @@ function renderHeaderCol($column, column, $header, kanbanData)
}
var moreAction = ' <button class="btn btn-link action" title="' + kanbanLang.moreAction + '" data-contextmenu="column" data-column="' + column.id + '"><i class="icon icon-ellipsis-v"></i></button>';
$actions.html(addItemBtn + moreAction);
if(CRKanban || kanban.status != 'closed') $actions.html(addItemBtn + moreAction);
}
if(columnPrivs.includes('sortColumn'))
@@ -215,7 +219,7 @@ function renderLaneName($lane, lane, $kanban, columns, kanban)
$lane.parent().toggleClass('sort', canSort);
if(!$lane.children('.actions').length && (canSet || canDelete))
if(!$lane.children('.actions').length && (canSet || canDelete) && (CRKanban || kanbanInfo.status != 'closed') )
{
$([
'<div class="actions" title="' + kanbanLang.more + '">',
@@ -282,7 +286,7 @@ function renderKanbanItem(item, $item)
var printMoreBtn = (privs.includes('editCard') || privs.includes('archiveCard') || privs.includes('copyCard') || privs.includes('deleteCard') || privs.includes('moveCard') || privs.includes('setCardColor'));
var $actions = $item.children('.actions');
var $title = $item.children('.title');
if(printMoreBtn && !$actions.length)
if(printMoreBtn && !$actions.length && (CRKanban || kanban.status != 'closed'))
{
$(
[
@@ -958,7 +962,7 @@ function findDropColumns($element, $root)
return $root.find('.kanban-lane-col:not([data-type="EMPTY"],[data-type=""])').filter(function()
{
if($.cookie('isFullScreen') == 1) return false;
if($.cookie('isFullScreen') == 1 || (!CRKanban && kanbanInfo.status == 'closed')) return false;
var $newCol = $(this);
var newCol = $newCol.data();
var $newLane = $newCol.closest('.kanban-lane');
@@ -1223,10 +1227,15 @@ function handleSortCards(event)
{
var newLaneID = event.element.closest('.kanban-lane').data('id');
var newColID = event.element.closest('.kanban-col').data('id');
var orders = [];
event.list.each(function(_, item){orders.push(item.item.data('id'));});
var url = createLink('kanban', 'sortCard', 'kanbanID=' + kanbanID + '&laneID=' + newLaneID + '&columnID=' + newColID + '&cards=' + orders.join(','));
var cards = event.element.closest('.kanban-lane-items').data('cards');
var orders = cards.map(function(card){return card.id});
var fromID = String(event.element.data('id'));
var toID = String(event.target.data('id'));
orders.splice(orders.indexOf(fromID), 1);
orders.splice(orders.indexOf(toID) + (event.insert === 'before' ? 0 : 1), 0, fromID);
var url = createLink('kanban', 'sortCard', 'kanbanID=' + kanbanID + '&laneID=' + newLaneID + '&columnID=' + newColID + '&cards=' + orders.join(','));
$.getJSON(url, function(response)
{
if(response.result === 'fail')
@@ -1277,6 +1286,8 @@ function initKanban($kanban)
onRenderHeaderCol: renderHeaderCol,
onRenderCount: renderCount,
sortable: handleSortCards,
virtualize: true,
virtualCardList: true,
droppable:
{
target: findDropColumns,
@@ -1298,6 +1309,8 @@ $(function()
{
window.isMultiLanes = laneCount > 1;
$.cookie('isFullScreen', 0);
/* Init first kanban */
$('.kanban').each(function()
{
@@ -1408,12 +1421,14 @@ $(function()
initSortable();
resetRegionHeight('open');
if(!CRKanban && kanbanInfo.status == 'closed') $('.kanban-col.kanban-header-col').css('padding', '0px 0px 0px 0px');
});
function initSortable()
{
var sortType = '';
var $cards = null;
if(!CRKanban && kanbanInfo.status == 'closed') return;
$('#kanban').sortable(
{
selector: '.region, .kanban-board, .kanban-lane, .kanban-col',
+2
View File
@@ -8,6 +8,7 @@ $lang->kanban->deleteSpace = 'Delete Space';
$lang->kanban->sortSpace = 'Sort Space';
$lang->kanban->edit = 'Edit Kanban';
$lang->kanban->view = 'View Kanban';
$lang->kanban->activate = 'Activate Kanban';
$lang->kanban->close = 'Close Kanban';
$lang->kanban->delete = 'Delete Kanban';
$lang->kanban->createRegion = 'Create Region';
@@ -334,6 +335,7 @@ $lang->kanbancard->delete = 'Delete';
$lang->kanbancard->name = 'Card Name';
$lang->kanbancard->legendBasicInfo = 'Basic Info';
$lang->kanbancard->legendLifeTime = 'Card Life';
$lang->kanbancard->legendDesc = 'Card Description';
$lang->kanbancard->space = 'Space';
$lang->kanbancard->region = 'Region';
$lang->kanbancard->kanban = 'Kanban';
+2
View File
@@ -8,6 +8,7 @@ $lang->kanban->deleteSpace = '删除空间';
$lang->kanban->sortSpace = '空间排序';
$lang->kanban->edit = '设置看板';
$lang->kanban->view = '查看看板';
$lang->kanban->activate = '激活看板';
$lang->kanban->close = '关闭看板';
$lang->kanban->delete = '删除看板';
$lang->kanban->createRegion = '新增区域';
@@ -334,6 +335,7 @@ $lang->kanbancard->delete = '删除';
$lang->kanbancard->name = '卡片名称';
$lang->kanbancard->legendBasicInfo = '基本信息';
$lang->kanbancard->legendLifeTime = '卡片的一生';
$lang->kanbancard->legendDesc = '卡片描述';
$lang->kanbancard->space = '所属空间';
$lang->kanbancard->region = '所属区域';
$lang->kanbancard->kanban = '所属看板';
+90 -15
View File
@@ -1114,10 +1114,11 @@ class kanbanModel extends model
* @param object $cards
* @param string $fromType
* @param int $archived
* @param int $regionID
* @access public
* @return array
*/
public function getImportedCards($kanbanID, $cards, $fromType, $archived = 0)
public function getImportedCards($kanbanID, $cards, $fromType, $archived = 0, $regionID = 0)
{
/* Get imported cards based on imported object type. */
$objectCards = $this->dao->select('*')->from(TABLE_KANBANCARD)
@@ -1125,6 +1126,7 @@ class kanbanModel extends model
->andWhere('kanban')->eq($kanbanID)
->andWhere('archived')->eq($archived)
->andWhere('fromType')->eq($fromType)
->beginIF($regionID)->andWhere('region')->eq($regionID)->fi()
->fetchGroup('fromID', 'id');
if(!empty($objectCards))
@@ -1295,7 +1297,9 @@ class kanbanModel extends model
if($cell->type == 'task')
{
$cardData['name'] = $object->name;
$cardData['name'] = $object->name;
$cardData['status'] = $object->status;
$cardData['left'] = $object->left;
}
else
{
@@ -1402,7 +1406,9 @@ class kanbanModel extends model
if($lane->type == 'task')
{
$cardData['name'] = $object->name;
$cardData['name'] = $object->name;
$cardData['status'] = $object->status;
$cardData['left'] = $object->left;
}
else
{
@@ -1515,7 +1521,9 @@ class kanbanModel extends model
if($browseType == 'task')
{
$cardData['name'] = $object->name;
$cardData['name'] = $object->name;
$cardData['status'] = $object->status;
$cardData['left'] = $object->left;
}
else
{
@@ -2045,6 +2053,37 @@ class kanbanModel extends model
}
}
/**
* Activate a kanban.
*
* @param int $kanbanID
* @access public
* @return array
*/
function activate($kanbanID)
{
$kanbanID = (int)$kanbanID;
$oldKanban = $this->getByID($kanbanID);
$now = helper::now();
$kanban = fixer::input('post')
->setDefault('status', 'active')
->setDefault('activatedBy', $this->app->user->account)
->setDefault('activatedDate', $now)
->setDefault('closedBy', '')
->setDefault('closedDate', '0000-00-00 00:00:00')
->setDefault('lastEditedBy', $this->app->user->account)
->setDefault('lastEditedDate', $now)
->remove('comment')
->get();
$this->dao->update(TABLE_KANBAN)->data($kanban)
->autoCheck()
->where('id')->eq($kanbanID)
->exec();
if(!dao::isError()) return common::createChanges($oldKanban, $kanban);
}
/**
* Close a kanban.
*
@@ -2061,6 +2100,8 @@ class kanbanModel extends model
->setDefault('status', 'closed')
->setDefault('closedBy', $this->app->user->account)
->setDefault('closedDate', $now)
->setDefault('activatedBy', '')
->setDefault('activatedDate', '0000-00-00 00:00:00')
->setDefault('lastEditedBy', $this->app->user->account)
->setDefault('lastEditedDate', $now)
->remove('comment')
@@ -2940,33 +2981,35 @@ class kanbanModel extends model
$actions .= "<div class='btn-group'>";
$actions .= "<a href='javascript:fullScreen();' id='fullScreenBtn' $btnColor class='btn btn-link'><i class='icon icon-fullscreen'></i> {$this->lang->kanban->fullScreen}</a>";
$printSettingBtn = (common::hasPriv('kanban', 'createRegion') or $printSetHeightBtn or common::hasPriv('kanban', 'performable') or common::hasPriv('kanban', 'edit') or common::hasPriv('kanban', 'close') or common::hasPriv('kanban', 'enableArchived') or common::hasPriv('kanban', 'delete'));
$CRKanban = !(isset($this->config->CRKanban) and $this->config->CRKanban == '0' and $kanban->status == 'closed');
$printRegionBtn = ($CRKanban and (common::hasPriv('kanban', 'createRegion') or $printSetHeightBtn or common::hasPriv('kanban', 'performable') or common::hasPriv('kanban', 'enableArchived') or common::hasPriv('kanban', 'import') or common::hasPriv('kanban', 'setColumnWidth')));
$printKanbanBtn = (common::hasPriv('kanban', 'edit') or ($kanban->status == 'active' and common::hasPriv('kanban', 'close')) or common::hasPriv('kanban', 'delete') or ($kanban->status == 'closed' and common::hasPriv('kanban', 'activate')));
if($printSettingBtn)
if($printRegionBtn or $printKanbanBtn)
{
$actions .= "<a data-toggle='dropdown' $btnColor class='btn btn-link dropdown-toggle setting' type='button'>" . '<i class="icon icon-cog-outline"></i> ' . $this->lang->kanban->setting . '</a>';
$actions .= "<ul id='kanbanActionMenu' class='dropdown-menu text-left'>";
if(common::hasPriv('kanban', 'createRegion')) $actions .= '<li>' . html::a(helper::createLink('kanban', 'createRegion', "kanbanID=$kanban->id", '', true), '<i class="icon icon-plus"></i>' . $this->lang->kanban->createRegion, '', "class='iframe btn btn-link'") . '</li>';
if(common::hasPriv('kanban', 'createRegion') and $CRKanban) $actions .= '<li>' . html::a(helper::createLink('kanban', 'createRegion', "kanbanID=$kanban->id", '', true), '<i class="icon icon-plus"></i>' . $this->lang->kanban->createRegion, '', "class='iframe btn btn-link'") . '</li>';
$importWidth = $this->app->getClientLang() == 'en' ? '700' : '550';
if(common::hasPriv('kanban', 'import')) $actions .= '<li>' . html::a(helper::createLink('kanban', 'import', "kanbanID=$kanban->id", '', true), '<i class="icon icon-import"></i>' . $this->lang->kanban->import, '', "class='iframe btn btn-link' data-width=$importWidth") . '</li>';
if(common::hasPriv('kanban', 'enableArchived')) $actions .= '<li>' . html::a(helper::createLink('kanban', 'enableArchived', "kanbanID=$kanban->id", '', true), '<i class="icon icon-card-archive"></i>' . $this->lang->kanban->archived, '', "class='iframe btn btn-link' data-width=400") . '</li>';
if($printSetHeightBtn)
if(common::hasPriv('kanban', 'import') and $CRKanban) $actions .= '<li>' . html::a(helper::createLink('kanban', 'import', "kanbanID=$kanban->id", '', true), '<i class="icon icon-import"></i>' . $this->lang->kanban->import, '', "class='iframe btn btn-link' data-width=$importWidth") . '</li>';
if(common::hasPriv('kanban', 'enableArchived') and $CRKanban) $actions .= '<li>' . html::a(helper::createLink('kanban', 'enableArchived', "kanbanID=$kanban->id", '', true), '<i class="icon icon-card-archive"></i>' . $this->lang->kanban->archived, '', "class='iframe btn btn-link' data-width=400") . '</li>';
if($printSetHeightBtn and $CRKanban)
{
$width = $this->app->getClientLang() == 'en' ? '750' : '650';
$actions .= '<li>' . html::a(helper::createLink('kanban', 'setLaneHeight', "kanbanID=$kanban->id", '', true), '<i class="icon icon-size-height"></i>' . $this->lang->kanban->laneHeight, '', "class='iframe btn btn-link' data-width='$width'") . '</li>';
}
if(common::hasPriv('kanban', 'setColumnWidth')) $actions .= '<li>' . html::a(helper::createLink('kanban', 'setColumnWidth', "kanbanID=$kanban->id", '', true), '<i class="icon icon-size-width"></i>' . $this->lang->kanban->columnWidth, '', "class='iframe btn btn-link' data-width=400") . '</li>';
if(common::hasPriv('kanban', 'performable')) $actions .= '<li>' . html::a(helper::createLink('kanban', 'performable', "kanbanID=$kanban->id", '', true), '<i class="icon icon-checked"></i>' . $this->lang->kanban->manageProgress, '', "class='iframe btn btn-link' data-width=40%") . '</li>';
if(common::hasPriv('kanban', 'setColumnWidth') and $CRKanban) $actions .= '<li>' . html::a(helper::createLink('kanban', 'setColumnWidth', "kanbanID=$kanban->id", '', true), '<i class="icon icon-size-width"></i>' . $this->lang->kanban->columnWidth, '', "class='iframe btn btn-link' data-width=400") . '</li>';
if(common::hasPriv('kanban', 'performable') and $CRKanban) $actions .= '<li>' . html::a(helper::createLink('kanban', 'performable', "kanbanID=$kanban->id", '', true), '<i class="icon icon-checked"></i>' . $this->lang->kanban->manageProgress, '', "class='iframe btn btn-link' data-width=40%") . '</li>';
$kanbanActions = '';
$attr = $kanban->status == 'closed' ? "disabled='disabled'" : '';
if(common::hasPriv('kanban', 'edit')) $kanbanActions .= '<li>' . html::a(helper::createLink('kanban', 'edit', "kanbanID=$kanban->id", '', true), '<i class="icon icon-edit"></i>' . $this->lang->kanban->edit, '', "class='iframe btn btn-link' data-width='75%'") . '</li>';
if(common::hasPriv('kanban', 'close')) $kanbanActions .= '<li>' . html::a(helper::createLink('kanban', 'close', "kanbanID=$kanban->id", '', true), '<i class="icon icon-off"></i>' . $this->lang->kanban->close, '', "class='iframe btn btn-link' $attr") . '</li>';
if(common::hasPriv('kanban', 'close') and $kanban->status == 'active') $kanbanActions .= '<li>' . html::a(helper::createLink('kanban', 'close', "kanbanID=$kanban->id", '', true), '<i class="icon icon-off"></i>' . $this->lang->kanban->close, '', "class='iframe btn btn-link'") . '</li>';
if(common::hasPriv('kanban', 'activate') and $kanban->status == 'closed') $kanbanActions .= '<li>' . html::a(helper::createLink('kanban', 'activate', "kanbanID=$kanban->id", '', true), '<i class="icon icon-magic"></i>' . $this->lang->kanban->activate, '', "class='iframe btn btn-link'") . '</li>';
if(common::hasPriv('kanban', 'delete')) $kanbanActions .= '<li>' . html::a(helper::createLink('kanban', 'delete', "kanbanID=$kanban->id"), '<i class="icon icon-trash"></i>' . $this->lang->kanban->delete, 'hiddenwin', "class='btn btn-link'") . '</li>';
if($kanbanActions)
{
$actions .= ((common::hasPriv('kanban', 'createRegion') or $printSetHeightBtn or common::hasPriv('kanban', 'performable') or common::hasPriv('kanban', 'setColumnWidth')) and (common::hasPriv('kanban', 'edit') or common::hasPriv('kanban', 'close') or common::hasPriv('kanban', 'enableArchived') or common::hasPriv('kanban', 'delete'))) ? "<li class='divider'></li>" . $kanbanActions : $kanbanActions;
$actions .= $printRegionBtn ? "<li class='divider'></li>" . $kanbanActions : $kanbanActions;
}
$actions .= "</ul>";
}
@@ -3572,6 +3615,38 @@ class kanbanModel extends model
return $menus;
}
/**
* Get toList and ccList.
*
* @param object $card
* @access public
* @return bool|array
*/
public function getToAndCcList($card)
{
/* Set toList and ccList. */
$toList = $card->createdBy;
$ccList = trim($card->assignedTo, ',');
if(empty($toList))
{
if(empty($ccList)) return false;
if(strpos($ccList, ',') === false)
{
$toList = $ccList;
$ccList = '';
}
else
{
$commaPos = strpos($ccList, ',');
$toList = substr($ccList, 0, $commaPos);
$ccList = substr($ccList, $commaPos + 1);
}
}
return array($toList, $ccList);
}
/**
* Check if user can execute an action.
*

Some files were not shown because too many files have changed in this diff Show More