Merge branch 'master' of https://gitlab.zcorp.cc/easycorp/zentaopms
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* 禅道API的issue资源类
|
||||
* 版本V1
|
||||
*
|
||||
* The issue entry point of zentaopms
|
||||
* Version 1
|
||||
*/
|
||||
class issueEntry extends Entry
|
||||
{
|
||||
public function get($issueID)
|
||||
{
|
||||
$control = $this->loadController('issue', 'view');
|
||||
$control->view($issueID);
|
||||
|
||||
$data = $this->getData();
|
||||
if(!$data or (isset($data->message) and $data->message == '404 Not found')) return $this->send404();
|
||||
if(isset($data->status) and $data->status == 'success') $this->send(200, $this->format($data->data->issue, 'createdDate:time,editedDate:time,assignedDate:time'));
|
||||
if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message);
|
||||
|
||||
$this->sendError(400, 'error');
|
||||
}
|
||||
|
||||
public function put($issueID)
|
||||
{
|
||||
$oldIssue = $this->loadModel('issue')->getByID($issueID);
|
||||
|
||||
/* Set $_POST variables. */
|
||||
$fields = 'type,title,severity,pri,assignedTo,deadline,desc';
|
||||
$this->batchSetPost($fields, $oldIssue);
|
||||
|
||||
$control = $this->loadController('issue', 'edit');
|
||||
$control->edit($issueID);
|
||||
|
||||
$data = $this->getData();
|
||||
if(!$data or (isset($data->message) and $data->message == '404 Not found')) return $this->send404();
|
||||
if(isset($data->result) and $data->result == 'fail') return $this->sendError(400, $data->message);
|
||||
|
||||
$issue = $this->issue->getByID($issueID);
|
||||
$this->send(200, $this->format($issue, 'createdDate:time,editedDate:time,assignedDate:time'));
|
||||
}
|
||||
|
||||
public function delete($issueID)
|
||||
{
|
||||
$control = $this->loadController('issue', 'delete');
|
||||
$control->delete($issueID, 'true');
|
||||
|
||||
$this->getData();
|
||||
$this->sendSuccess(200, 'success');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 禅道API的issues资源类
|
||||
* 版本V1
|
||||
*
|
||||
* The issues entry point of zentaopms
|
||||
* Version 1
|
||||
*/
|
||||
class issuesEntry extends entry
|
||||
{
|
||||
public function get()
|
||||
{
|
||||
$control = $this->loadController('my', 'issue');
|
||||
$control->issue($this->param('type', 'assignedTo'), $this->param('order', 'id_desc'), $this->param('total', 0), $this->param('limit', 20), $this->param('page', 1));
|
||||
$data = $this->getData();
|
||||
|
||||
if(!isset($data->status)) return $this->sendError(400, 'error');
|
||||
if(isset($data->status) and $data->status == 'fail') return $this->sendError(400, $data->message);
|
||||
|
||||
$pager = $data->data->pager;
|
||||
$result = array();
|
||||
foreach($data->data->issues as $issue)
|
||||
{
|
||||
$result[] = $this->format($issue, 'createdDate:time,editedDate:time,assignedDate:time');
|
||||
}
|
||||
return $this->send(200, array('page' => $pager->pageID, 'total' => $pager->recTotal, 'limit' => $pager->recPerPage, 'issues' => $result));
|
||||
}
|
||||
|
||||
public function post($projectID = 0)
|
||||
{
|
||||
if((int) $projectID <= 0) $this->sendError(400, 'The id of project is wrong.');
|
||||
|
||||
$fields = 'type,title,severity,pri,assignedTo,deadline,desc';
|
||||
$this->batchSetPost($fields);
|
||||
|
||||
$control = $this->loadController('issue', 'create');
|
||||
$this->requireFields('type,title,severity');
|
||||
|
||||
$control->create($projectID);
|
||||
|
||||
$data = $this->getData();
|
||||
if(isset($data->result) and $data->result == 'fail') return $this->sendError(400, $data->message);
|
||||
if(isset($data->result) and!isset($data->id)) return $this->sendError(400, $data->message);
|
||||
|
||||
$issue = $this->loadModel('issue')->getByID($data->id);
|
||||
|
||||
$this->send(201, $this->format($issue, 'createdDate:time,editedDate:time,assignedDate:time'));
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -222,7 +222,8 @@ $filter->project->task->cookie['projectTaskOrder'] = 'reg::orderBy';
|
||||
$filter->project->task->cookie['windowWidth'] = 'int';
|
||||
$filter->project->export->cookie['checkedItem'] = 'reg::checked';
|
||||
|
||||
$filter->projectstory->story->cookie['storyModuleParam'] = 'int';
|
||||
$filter->projectstory->story->cookie['storyModuleParam'] = 'int';
|
||||
$filter->projectstory->story->cookie['pagerProductBrowse'] = 'int';
|
||||
|
||||
$filter->qa->default->cookie['lastProduct'] = 'int';
|
||||
$filter->qa->default->cookie['preBranch'] = 'int';
|
||||
|
||||
@@ -55,4 +55,8 @@ $routes['/projects/:project/risks'] = 'risks';
|
||||
$routes['/risks'] = 'risks';
|
||||
$routes['/risks/:id'] = 'risk';
|
||||
|
||||
$routes['/projects/:project/questions'] = 'issues';
|
||||
$routes['/questions'] = 'issues';
|
||||
$routes['/questions/:id'] = 'issue';
|
||||
|
||||
$config->routes = $routes;
|
||||
|
||||
@@ -897,7 +897,8 @@ class baseControl
|
||||
foreach($data as $key => $value)
|
||||
{
|
||||
if(!is_string($value)) continue;
|
||||
$data[$key] = urlencode($value);
|
||||
/* Retain ["] for json encode when value is jsoned string. */
|
||||
$data[$key] = str_replace('%22', '"', urlencode($value));
|
||||
}
|
||||
|
||||
print(urldecode(json_encode($data)));
|
||||
|
||||
@@ -211,6 +211,115 @@ class html extends baseHTML
|
||||
$value = str_replace("'", ''', $value);
|
||||
return "<input type='number' name='$name' {$id} value='$value' $attrib />\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to a uni code
|
||||
*
|
||||
* @param string $string
|
||||
* @return int
|
||||
*/
|
||||
static public function stringToCode($string)
|
||||
{
|
||||
$stringLength = strlen($string);
|
||||
if($stringLength == 0) return 0;
|
||||
|
||||
$code = 0;
|
||||
for($i = 0; $i < $stringLength; ++$i)
|
||||
{
|
||||
$code += ($i + 1) * ord($string[$i]);
|
||||
}
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create user avatar.
|
||||
*
|
||||
* @param string|object|array $user User object or user avatar address or user account
|
||||
* @param string|int $size Avatar size, can be a number or preset sizes: "xs", "sm", "", "lg", "xl", default is ""
|
||||
* @param string $className Avatar element class name, default is "avatar-circle"
|
||||
* @param string $attrib Extra attributes on avatar element
|
||||
* @param string $tag Avatar element tag name, default is "div"
|
||||
* @static
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
static public function avatar($user, $size = '', $className = 'avatar-circle', $attrib = '', $tag = 'div')
|
||||
{
|
||||
if(is_string($user))
|
||||
{
|
||||
$userObj = new stdClass();
|
||||
if(strlen($user) > 1) $userObj->avatar = $user;
|
||||
else $userObj->account = $user;
|
||||
$user = $userObj;
|
||||
}
|
||||
else if(is_array($user))
|
||||
{
|
||||
$userObj = new stdClass();
|
||||
$userObj->avatar = $user['avatar'];
|
||||
$userObj->account = $user['account'];
|
||||
$user = $userObj;
|
||||
}
|
||||
|
||||
$hasImage = !empty($user->avatar);
|
||||
|
||||
$extraClassName = $hasImage ? ' has-img' : ' has-text';
|
||||
$style = '';
|
||||
|
||||
if($size)
|
||||
{
|
||||
if(is_numeric($size)) $style .= "width: $size" . "px; height: $size" . "px; line-height: $size" . 'px;';
|
||||
$extraClassName .= " avatar-$size";
|
||||
}
|
||||
|
||||
if(!$hasImage)
|
||||
{
|
||||
$colorHue = (html::stringToCode($user->account) * 43) % 360;
|
||||
$style .= "background: hsl($colorHue, 100%, 58%);";
|
||||
}
|
||||
|
||||
if(!empty($style)) $style = "style='$style'";
|
||||
|
||||
$html = "<$tag class='avatar$extraClassName $className' $attrib $style>";
|
||||
|
||||
if($hasImage) $html .= html::image($user->avatar);
|
||||
else $html .= '<span>' . strtoupper($user->account[0]) . '</span>';
|
||||
|
||||
$html .= "</$tag>";
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a small user avatar.
|
||||
*
|
||||
* @param string|object $user User object or user avatar address or user account
|
||||
* @param string $className Avatar element class name, default is "avatar-circle"
|
||||
* @param string $attrib Extra attributes on avatar element
|
||||
* @param string $tag Avatar element tag name, default is "div"
|
||||
* @static
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
static public function smallAvatar($user, $className = 'avatar-circle', $attrib = '', $tag = 'div')
|
||||
{
|
||||
return html::avatar($user, 'sm', $className, $attrib, $tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a large user avatar.
|
||||
*
|
||||
* @param string|object $user User object or user avatar address or user account
|
||||
* @param string $className Avatar element class name, default is "avatar-circle"
|
||||
* @param string $attrib Extra attributes on avatar element
|
||||
* @param string $tag Avatar element tag name, default is "div"
|
||||
* @static
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
static public function largeAvatar($user, $className = 'avatar-circle', $attrib = '', $tag = 'div')
|
||||
{
|
||||
return html::avatar($user, 'lg', $className, $attrib, $tag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#cards .project-detail .progress-text-left .progress-text {width: 50px; left: -50px;}
|
||||
#cards .panel-heading {cursor: pointer;}
|
||||
#cards .project-stages-container {margin: 0 0 -16px 0; padding: 0 4px; height: 46px; overflow-x: auto; position: relative;}
|
||||
#cards .project-stages:after {content: ' '; width: 30px; display: block; right: -16px; top: 16px; bottom: -6px; z-index: 1; background: linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 100%); position: absolute;}
|
||||
#cards .project-stages:after {content: ' '; width: 30px; display: block; right: -5px; top: 16px; bottom: -5px; z-index: 1; background: linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 100%); position: absolute;}
|
||||
#cards .project-stages-row {position: relative; height: 30px; z-index: 0;}
|
||||
#cards .project-stage-item {white-space: nowrap; position: absolute; top: 0; min-width: 48px; padding-top: 13px; color: #838A9D;}
|
||||
#cards .project-stage-item > div {white-space: nowrap; overflow: visible; text-align: center; text-overflow: ellipsis;}
|
||||
@@ -73,7 +73,7 @@
|
||||
<span><i class='icon icon-clock'></i> <?php printf($lang->project->hoursUnit, $project->estimate); ?></span>
|
||||
</div>
|
||||
<?php if($config->systemMode == 'new'):?>
|
||||
<?php if($project->model === 'waterfall'): ?>
|
||||
<?php if($project->model !== 'waterfall'): ?>
|
||||
<div class='project-detail project-stages'>
|
||||
<?php
|
||||
$projectProjects = array();
|
||||
|
||||
@@ -2901,77 +2901,6 @@ class bugModel extends model
|
||||
echo !common::hasPriv('bug', 'assignTo', $bug) ? "<span style='padding-left: 21px' class='{$btnTextClass}'>{$assignedToText}</span>" : $assignToHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send mail
|
||||
*
|
||||
* @param int $bugID
|
||||
* @param int $actionID
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function sendmail($bugID, $actionID)
|
||||
{
|
||||
$this->loadModel('mail');
|
||||
$bug = $this->getByID($bugID);
|
||||
$users = $this->loadModel('user')->getPairs('noletter');
|
||||
|
||||
/* Get action info. */
|
||||
$action = $this->loadModel('action')->getById($actionID);
|
||||
$history = $this->action->getHistory($actionID);
|
||||
$action->history = isset($history[$actionID]) ? $history[$actionID] : array();
|
||||
$action->appendLink = '';
|
||||
if(strpos($action->extra, ':') !== false)
|
||||
{
|
||||
list($extra, $id) = explode(':', $action->extra);
|
||||
if($id and is_numeric($id))
|
||||
{
|
||||
$action->extra = $extra;
|
||||
|
||||
$name = $this->dao->select('title')->from(TABLE_BUG)->where('id')->eq($id)->fetch('title');
|
||||
if($name) $action->appendLink = html::a(zget($this->config->mail, 'domain', common::getSysURL()) . helper::createLink($action->objectType, 'view', "id=$id", 'html'), "#$id " . $name);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get mail content. */
|
||||
$modulePath = $this->app->getModulePath($appName = '', 'bug');
|
||||
$oldcwd = getcwd();
|
||||
$viewFile = $modulePath . 'view/sendmail.html.php';
|
||||
chdir($modulePath . 'view');
|
||||
if(file_exists($modulePath . 'ext/view/sendmail.html.php'))
|
||||
{
|
||||
$viewFile = $modulePath . 'ext/view/sendmail.html.php';
|
||||
chdir($modulePath . 'ext/view');
|
||||
}
|
||||
ob_start();
|
||||
include $viewFile;
|
||||
foreach(glob($modulePath . 'ext/view/sendmail.*.html.hook.php') as $hookFile) include $hookFile;
|
||||
$mailContent = ob_get_contents();
|
||||
ob_end_clean();
|
||||
chdir($oldcwd);
|
||||
|
||||
$sendUsers = $this->getToAndCcList($bug);
|
||||
if(!$sendUsers) return;
|
||||
list($toList, $ccList) = $sendUsers;
|
||||
$subject = $this->getSubject($bug);
|
||||
|
||||
/* Send it. */
|
||||
$this->mail->send($toList, $subject, $mailContent, $ccList);
|
||||
if($this->mail->isError()) error_log(join("\n", $this->mail->getError()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subject.
|
||||
*
|
||||
* @param object $bug
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getSubject($bug)
|
||||
{
|
||||
$productName = $this->loadModel('product')->getById($bug->product)->name;
|
||||
return 'BUG #'. $bug->id . ' ' . $bug->title . ' - ' . $productName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get toList and ccList.
|
||||
*
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
* @link http://www.zentao.net
|
||||
*/
|
||||
?>
|
||||
<?php $mailTitle = 'BUG #' . $bug->id . ' ' . $bug->title;?>
|
||||
<?php $mailTitle = 'BUG #' . $object->id . ' ' . $object->title;?>
|
||||
<?php include $this->app->getModuleRoot() . 'common/view/mail.header.html.php';?>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellpadding='0' cellspacing='0' width='600' style='border: none; border-collapse: collapse;'>
|
||||
<tr>
|
||||
<td style='padding: 10px; background-color: #F8FAFE; border: none; font-size: 14px; font-weight: 500; border-bottom: 1px solid #e5e5e5;'>
|
||||
<?php $color = empty($bug->color) ? '#333' : $bug->color;?>
|
||||
<?php echo html::a(zget($this->config->mail, 'domain', common::getSysURL()) . helper::createLink('bug', 'view', "bugID=$bug->id", 'html'), $mailTitle, '', "style='color: {$color}; text-decoration: underline;'");?>
|
||||
<?php $color = empty($object->color) ? '#333' : $object->color;?>
|
||||
<?php echo html::a(zget($this->config->mail, 'domain', common::getSysURL()) . helper::createLink('bug', 'view', "bugID=$object->id", 'html'), $mailTitle, '', "style='color: {$color}; text-decoration: underline;'");?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -28,7 +28,7 @@
|
||||
<td style='padding: 10px; border: none;'>
|
||||
<fieldset style='border: 1px solid #e5e5e5'>
|
||||
<legend style='color: #114f8e'><?php echo $this->lang->bug->legendSteps;?></legend>
|
||||
<div style='padding:5px;'><?php echo $bug->steps;?></div>
|
||||
<div style='padding:5px;'><?php echo $object->steps;?></div>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -281,9 +281,7 @@ class commonModel extends model
|
||||
$isGuest = $app->user->account == 'guest';
|
||||
|
||||
echo "<a class='dropdown-toggle' data-toggle='dropdown'>";
|
||||
echo "<div id='main-avatar' class='avatar avatar bg-secondary avatar-circle'>";
|
||||
echo !empty($app->user->avatar) ? html::image($app->user->avatar) : strtoupper($app->user->account[0]);
|
||||
echo "</div>\n";
|
||||
echo html::avatar($app->user);
|
||||
echo '</a>';
|
||||
echo "<ul class='dropdown-menu pull-right'>";
|
||||
if(!$isGuest)
|
||||
@@ -291,9 +289,7 @@ class commonModel extends model
|
||||
$noRole = (!empty($app->user->role) && isset($lang->user->roleList[$app->user->role])) ? '' : ' no-role';
|
||||
echo '<li class="user-profile-item">';
|
||||
echo "<a href='" . helper::createLink('my', 'profile', '', '', true) . "' data-width='600' class='iframe $noRole'" . '>';
|
||||
echo "<div id='menu-avatar' class='avatar avatar bg-secondary avatar-circle'>";
|
||||
echo $app->user->avatar ? html::image($app->user->avatar) : strtoupper($app->user->account[0]);
|
||||
echo "</div>\n";
|
||||
echo html::avatar($app->user, '', 'avatar-circle', 'id="menu-avatar"');
|
||||
echo '<div class="user-profile-name">' . (empty($app->user->realname) ? $app->user->account : $app->user->realname) . '</div>';
|
||||
if(isset($lang->user->roleList[$app->user->role])) echo '<div class="user-profile-role">' . $lang->user->roleList[$app->user->role] . '</div>';
|
||||
echo '</a></li><li class="divider"></li>';
|
||||
|
||||
+35
-6
@@ -1,9 +1,24 @@
|
||||
/**
|
||||
* Load modules by libID.
|
||||
*
|
||||
* @param int $libID
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
function loadModules(libID)
|
||||
{
|
||||
link = createLink('doc', 'ajaxGetModules', 'libID=' + libID);
|
||||
$('#moduleBox').load(link, function(){$('#moduleBox').find('select').chosen()});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle acl.
|
||||
*
|
||||
* @param string $acl
|
||||
* @param string $type
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
function toggleAcl(acl, type)
|
||||
{
|
||||
if(acl == 'custom')
|
||||
@@ -27,6 +42,13 @@ function toggleAcl(acl, type)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load doc module by libID.
|
||||
*
|
||||
* @param int $libID
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
function loadDocModule(libID)
|
||||
{
|
||||
link = createLink('doc', 'ajaxGetChild', 'libID=' + libID);
|
||||
@@ -38,6 +60,13 @@ function loadDocModule(libID)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cookie of browse type and reload.
|
||||
*
|
||||
* @param type $type
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
function setBrowseType(type)
|
||||
{
|
||||
$.cookie('browseType', type, {expires:config.cookieLife, path:config.webRoot});
|
||||
@@ -46,7 +75,7 @@ function setBrowseType(type)
|
||||
|
||||
$(document).ready(function()
|
||||
{
|
||||
/* hide #module chosen dropdown on #lib dropdown show */
|
||||
/* Hide #module chosen dropdown on #lib dropdown show. */
|
||||
$('#lib').on('chosen:showing_dropdown', function()
|
||||
{
|
||||
$('#module').trigger('chosen:close');
|
||||
@@ -83,9 +112,9 @@ $(document).ready(function()
|
||||
|
||||
'use strict';
|
||||
|
||||
var NAME = 'zui.splitRow'; // model name
|
||||
var NAME = 'zui.splitRow'; // model name.
|
||||
|
||||
/* The SplitRow model class */
|
||||
/* The SplitRow model class. */
|
||||
var SplitRow = function(element, options)
|
||||
{
|
||||
var that = this;
|
||||
@@ -181,7 +210,7 @@ $(document).ready(function()
|
||||
resizeCols();
|
||||
};
|
||||
|
||||
/* default options */
|
||||
/* default options. */
|
||||
SplitRow.DEFAULTS =
|
||||
{
|
||||
spliter: '<div class="col-spliter"></div>',
|
||||
@@ -189,7 +218,7 @@ $(document).ready(function()
|
||||
middleSize: 850
|
||||
};
|
||||
|
||||
/* Extense jquery element */
|
||||
/* Extense jquery element. */
|
||||
$.fn.splitRow = function(option)
|
||||
{
|
||||
return this.each(function()
|
||||
@@ -205,7 +234,7 @@ $(document).ready(function()
|
||||
|
||||
$.fn.splitRow.Constructor = SplitRow;
|
||||
|
||||
/* Auto call splitRow after document load complete */
|
||||
/* Auto call splitRow after document load complete. */
|
||||
$(function()
|
||||
{
|
||||
$('.split-row').splitRow();
|
||||
|
||||
@@ -21,7 +21,7 @@ $(function()
|
||||
$('.ke-toolbar .ke-outline:last').after("<span data-name='unlink' class='ke-outline' title='Markdown' onclick='toggleEditor(\"markdown\")' style='font-size: unset; line-height: unset;'>Markdown</span>");
|
||||
}
|
||||
|
||||
$(document).on("mousedown", 'span[data-name="fullscreen"]', function()
|
||||
$(document).on("mouseup", 'span[data-name="fullscreen"]', function()
|
||||
{
|
||||
if($(this).hasClass('ke-selected'))
|
||||
{
|
||||
@@ -35,7 +35,7 @@ $(function()
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("mousedown", 'a[title="Fullscreen"],.icon-columns', function()
|
||||
$(document).on("mouseup", 'a[title="Fullscreen"],.icon-columns', function()
|
||||
{
|
||||
setTimeout(function()
|
||||
{
|
||||
|
||||
@@ -33,7 +33,7 @@ $(function()
|
||||
/* Automatically save document contents. */
|
||||
setInterval("saveTempContent()", 60 * 1000);
|
||||
|
||||
$(document).on("mousedown", 'span[data-name="fullscreen"]', function()
|
||||
$(document).on("mouseup", 'span[data-name="fullscreen"]', function()
|
||||
{
|
||||
if(config.onlybody == 'no')
|
||||
{
|
||||
@@ -50,7 +50,7 @@ $(function()
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("mousedown", 'a[title="Fullscreen"],.icon-columns', function()
|
||||
$(document).on("mouseup", 'a[title="Fullscreen"],.icon-columns', function()
|
||||
{
|
||||
if(config.onlybody == 'no')
|
||||
{
|
||||
|
||||
+3
-81
@@ -1875,10 +1875,10 @@ class docModel extends model
|
||||
if($doc->type == 'project') $objectID = $doc->project;
|
||||
if($doc->type == 'execution') $objectID = $doc->execution;
|
||||
|
||||
$app = $this->app->openApp;
|
||||
if($app != 'doc') $app = $objectID ? $doc->type : 'doc';
|
||||
$tab = $this->app->openApp;
|
||||
if($tab != 'doc') $tab = $objectID ? $doc->type : 'doc';
|
||||
|
||||
$html .= '<li>' . html::a(inlink('objectLibs', "type={$doc->type}&objectID=$objectID&libID={$doc->lib}&docID={$doc->id}"), "<i class='icon icon-file-text'></i> " . $doc->title, '', "data-app='$app' title='{$doc->title}'") . '</li>';
|
||||
$html .= '<li>' . html::a(inlink('objectLibs', "type={$doc->type}&objectID=$objectID&libID={$doc->lib}&docID={$doc->id}"), "<i class='icon icon-file-text'></i> " . $doc->title, '', "data-app='$tab' title='{$doc->title}'") . '</li>';
|
||||
}
|
||||
|
||||
$collectionCount = $this->dao->select('count(id) as count')->from(TABLE_DOC)
|
||||
@@ -1935,84 +1935,6 @@ class docModel extends model
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send mail.
|
||||
*
|
||||
* @param int $docID
|
||||
* @param int $actionID
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function sendmail($docID, $actionID)
|
||||
{
|
||||
/* Load module and get doc and users. */
|
||||
$this->loadModel('mail');
|
||||
$doc = $this->getById($docID);
|
||||
$users = $this->loadModel('user')->getPairs('noletter');
|
||||
|
||||
/* When the content type is markdown format, add attributes to the table. */
|
||||
if($doc->contentType == 'markdown')
|
||||
{
|
||||
$doc->content = $this->app->loadClass('hyperdown')->makeHtml($doc->content);
|
||||
$doc->content = str_replace("<table>", "<table style='border-collapse: collapse;'>", $doc->content);
|
||||
$doc->content = str_replace("<th>", "<th style='word-break: break-word; border:1px solid #000;'>", $doc->content);
|
||||
$doc->content = str_replace("<td>", "<td style='word-break: break-word; border:1px solid #000;'>", $doc->content);
|
||||
}
|
||||
|
||||
/* Get action info. */
|
||||
$action = $this->loadModel('action')->getById($actionID);
|
||||
$history = $this->action->getHistory($actionID);
|
||||
$action->history = isset($history[$actionID]) ? $history[$actionID] : array();
|
||||
|
||||
/* Get mail content. */
|
||||
$modulePath = $this->app->getModulePath($appName = '', 'doc');
|
||||
$oldcwd = getcwd();
|
||||
$viewFile = $modulePath . 'view/sendmail.html.php';
|
||||
chdir($modulePath . 'view');
|
||||
if(file_exists($modulePath . 'ext/view/sendmail.html.php'))
|
||||
{
|
||||
$viewFile = $modulePath . 'ext/view/sendmail.html.php';
|
||||
chdir($modulePath . 'ext/view');
|
||||
}
|
||||
ob_start();
|
||||
include $viewFile;
|
||||
foreach(glob($modulePath . 'ext/view/sendmail.*.html.hook.php') as $hookFile) include $hookFile;
|
||||
$mailContent = ob_get_contents();
|
||||
ob_end_clean();
|
||||
chdir($oldcwd);
|
||||
|
||||
/* Get sender and subject. */
|
||||
$sendUsers = $this->getToAndCcList($doc);
|
||||
if(!$sendUsers) return;
|
||||
list($toList, $ccList) = $sendUsers;
|
||||
$subject = $this->getSubject($doc, $action->action);
|
||||
|
||||
/* Send mail. */
|
||||
$this->mail->send($toList, $subject, $mailContent, $ccList);
|
||||
if($this->mail->isError()) error_log(join("\n", $this->mail->getError()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mail subject.
|
||||
*
|
||||
* @param object $doc
|
||||
* @param string $actionType created|edited
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getSubject($doc, $actionType)
|
||||
{
|
||||
/* Set email title. */
|
||||
if($actionType == 'created')
|
||||
{
|
||||
return sprintf($this->lang->doc->mail->create->title, $this->app->user->realname, $doc->id, $doc->title);
|
||||
}
|
||||
else
|
||||
{
|
||||
return sprintf($this->lang->doc->mail->edit->title, $this->app->user->realname, $doc->id, $doc->title);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get toList and ccList.
|
||||
*
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
* @link https://www.zentao.net
|
||||
*/
|
||||
?>
|
||||
<?php $mailTitle = $this->lang->doc->common . ' #' . $doc->id . ' ' . $doc->title;?>
|
||||
<?php $mailTitle = $this->lang->doc->common . ' #' . $object->id . ' ' . $object->title;?>
|
||||
<?php include $this->app->getModuleRoot() . 'common/view/mail.header.html.php';?>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellpadding='0' cellspacing='0' width='600' style='border: none; border-collapse: collapse;'>
|
||||
<tr>
|
||||
<td style='padding: 10px; background-color: #F8FAFE; border: none; font-size: 14px; font-weight: 500; border-bottom: 1px solid #e5e5e5;'>
|
||||
<?php $color = empty($doc->color) ? '#333' : $doc->color;?>
|
||||
<?php echo html::a(zget($this->config->mail, 'domain', common::getSysURL()) . helper::createLink('doc', 'view', "docID=$doc->id", 'html'), $mailTitle, '', "style='color: {$color}; text-decoration: underline;'");?>
|
||||
<?php $color = empty($object->color) ? '#333' : $object->color;?>
|
||||
<?php echo html::a(zget($this->config->mail, 'domain', common::getSysURL()) . helper::createLink('doc', 'view', "docID=$object->id", 'html'), $mailTitle, '', "style='color: {$color}; text-decoration: underline;'");?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -28,7 +28,7 @@
|
||||
<td style='padding: 10px; border: none;'>
|
||||
<fieldset style='border: 1px solid #e5e5e5'>
|
||||
<legend style='color: #114f8e'><?php echo $this->lang->doc->content;?></legend>
|
||||
<div style='padding: 5px;'><?php echo $doc->content;?></div>
|
||||
<div style='padding: 5px;'><?php echo $object->content;?></div>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -27,3 +27,4 @@
|
||||
|
||||
#whitelistBox .checkbox-inline, #whitelistBox .radio-inline {margin-left: 10px; display: inline-block; padding-left: 0px; padding-right: 10px;}
|
||||
.main-table > .table-responsive {padding: 0 1px; background: #fff;}
|
||||
#teams_chosen ul li:first-child {max-width:256px; display: inline-block; vertical-align: middle;}
|
||||
|
||||
@@ -46,7 +46,7 @@ $(function()
|
||||
|
||||
$('#teams_chosen').click(function()
|
||||
{
|
||||
if(systemMode == 'new') $('#teams_chosen ul li:first').append(' <label class="label">' + projectCommon + '</label>');
|
||||
if(systemMode == 'new') $('#teams_chosen ul li:first').after(' <label class="label">' + projectCommon + '</label>');
|
||||
})
|
||||
|
||||
$('#teams').change(function()
|
||||
|
||||
@@ -358,7 +358,7 @@ class executionModel extends model
|
||||
->batchcheck($this->config->execution->create->requiredFields, 'notempty')
|
||||
->checkIF($sprint->begin != '', 'begin', 'date')
|
||||
->checkIF($sprint->end != '', 'end', 'date')
|
||||
->checkIF($sprint->end != '', 'end', 'gt', $sprint->begin)
|
||||
->checkIF($sprint->end != '', 'end', 'ge', $sprint->begin)
|
||||
->checkIF(!empty($sprint->code), 'code', 'unique')
|
||||
->exec();
|
||||
|
||||
@@ -2454,7 +2454,7 @@ class executionModel extends model
|
||||
{
|
||||
if(empty($projectID)) return array();
|
||||
|
||||
$teams = $this->dao->select('id,team,type')->from(TABLE_PROJECT)
|
||||
$teams = $this->dao->select('id,name,type')->from(TABLE_PROJECT)
|
||||
->where('deleted')->eq(0)
|
||||
->andWhere('(project')->eq($projectID)
|
||||
->orWhere('id')->eq($projectID)
|
||||
@@ -2466,10 +2466,10 @@ class executionModel extends model
|
||||
$teamPairs = array();
|
||||
foreach($teams as $id => $team)
|
||||
{
|
||||
if(empty($team->team)) continue;
|
||||
if(empty($team->name)) continue;
|
||||
|
||||
$prefix = ($team->type != 'project' and $this->config->systemMode == 'new') ? ' ' : '';
|
||||
$teamPairs[$id] = $prefix . $team->team;
|
||||
$teamPairs[$id] = $prefix . $team->name;
|
||||
}
|
||||
|
||||
return $teamPairs;
|
||||
|
||||
@@ -190,10 +190,13 @@ class gitlab extends control
|
||||
*/
|
||||
public function checkToken()
|
||||
{
|
||||
if(strpos($this->post->url, 'http') !== 0) return $this->send(array('result' => 'fail', 'message' => array('url' => array($this->lang->gitlab->hostError))));
|
||||
if(!$this->post->token) return $this->send(array('result' => 'fail', 'message' => array('token' => array($this->lang->gitlab->tokenError))));
|
||||
$gitlabURL = trim($this->post->url);
|
||||
$token = trim($this->post->token);
|
||||
|
||||
$user = $this->gitlab->apiGetCurrentUser($this->post->url, $this->post->token);
|
||||
if(strpos($gitlabURL, 'http') !== 0) return $this->send(array('result' => 'fail', 'message' => array('url' => array($this->lang->gitlab->hostError))));
|
||||
if(!$token) return $this->send(array('result' => 'fail', 'message' => array('token' => array($this->lang->gitlab->tokenError))));
|
||||
|
||||
$user = $this->gitlab->apiGetCurrentUser($gitlabURL, $token);
|
||||
|
||||
if(!is_object($user)) return $this->send(array('result' => 'fail', 'message' => array('url' => array($this->lang->gitlab->hostError))));
|
||||
if(!isset($user->is_admin) or !$user->is_admin) return $this->send(array('result' => 'fail', 'message' => array('token' => array($this->lang->gitlab->tokenError))));
|
||||
@@ -256,6 +259,11 @@ class gitlab extends control
|
||||
$gitlabID = $repo->gitlab;
|
||||
$projectID = $repo->project;
|
||||
|
||||
$gitlab = $this->gitlab->getByID($gitlabID);
|
||||
$user = $this->gitlab->apiGetCurrentUser($gitlab->url, $gitlab->token);
|
||||
if(!isset($user->is_admin) or !$user->is_admin) die(js::alert($this->lang->gitlab->tokenLimit) . js::locate($this->createLink('gitlab', 'edit', array('gitlabID' => $gitlabID))));
|
||||
|
||||
|
||||
if($_POST)
|
||||
{
|
||||
$executionList = $this->post->executionList;
|
||||
|
||||
@@ -40,6 +40,7 @@ $lang->gitlab->placeholder->token = "Please fill in the access token of an accou
|
||||
|
||||
$lang->gitlab->noImportableIssues = "There are currently no issues available for import.";
|
||||
$lang->gitlab->tokenError = "The current token is not admin rights.";
|
||||
$lang->gitlab->tokenLimit = "The current token has no admin privilege. Please regenerate one with admin user in GitLab.";
|
||||
$lang->gitlab->hostError = "Invalid GitLab service address.";
|
||||
$lang->gitlab->bindUserError = "Can not bind users repeatedly %s";
|
||||
$lang->gitlab->importIssueError = "The execution to which this issue belongs is not selected.";
|
||||
|
||||
@@ -661,4 +661,134 @@ class mailModel extends model
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send mail.
|
||||
*
|
||||
* @param int $objectID
|
||||
* @param int $actionID
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function sendmail($objectID, $actionID)
|
||||
{
|
||||
/* Load module and get vars. */
|
||||
$this->loadModel('action');
|
||||
$users = $this->loadModel('user')->getPairs('noletter');
|
||||
$action = $this->action->getById($actionID);
|
||||
$history = $this->action->getHistory($actionID);
|
||||
$objectType = $action->objectType;
|
||||
$object = $this->loadModel($objectType)->getByID($objectID);
|
||||
$nameFields = $this->config->action->objectNameFields[$objectType];
|
||||
$title = zget($object, $nameFields, '');
|
||||
$subject = $this->getSubject($objectType, $object, $title, $action->action);
|
||||
|
||||
if($objectType == 'review' and empty($object->auditedBy)) return;
|
||||
|
||||
if($objectType == 'doc')
|
||||
{
|
||||
if($object->contentType == 'markdown')
|
||||
{
|
||||
$object->content = $this->app->loadClass('hyperdown')->makeHtml($object->content);
|
||||
$object->content = str_replace("<table>", "<table style='border-collapse: collapse;'>", $object->content);
|
||||
$object->content = str_replace("<th>", "<th style='word-break: break-word; border:1px solid #000;'>", $object->content);
|
||||
$object->content = str_replace("<td>", "<td style='word-break: break-word; border:1px solid #000;'>", $object->content);
|
||||
}
|
||||
}
|
||||
|
||||
$action->history = isset($history[$actionID]) ? $history[$actionID] : array();
|
||||
$action->appendLink = '';
|
||||
if(strpos($action->extra, ':') !== false)
|
||||
{
|
||||
list($extra, $id) = explode(':', $action->extra);
|
||||
$action->extra = $extra;
|
||||
if($title)
|
||||
{
|
||||
$action->appendLink = html::a(zget($this->config->mail, 'domain', common::getSysURL()) . helper::createLink($action->objectType, 'view', "id=$id", 'html'), "#$id " . $title);
|
||||
}
|
||||
}
|
||||
|
||||
if($objectType == 'meeting') $rooms = $this->loadmodel('meetingroom')->getpairs();
|
||||
if($objectType == 'review') $this->app->loadLang('baseline');
|
||||
|
||||
/* Get mail content. */
|
||||
$modulePath = $this->app->getModulePath($appName = '', $objectType);
|
||||
$oldcwd = getcwd();
|
||||
$viewFile = $modulePath . 'view/sendmail.html.php';
|
||||
chdir($modulePath . 'view');
|
||||
if(file_exists($modulePath . 'ext/view/sendmail.html.php'))
|
||||
{
|
||||
$viewFile = $modulePath . 'ext/view/sendmail.html.php';
|
||||
chdir($modulePath . 'ext/view');
|
||||
}
|
||||
ob_start();
|
||||
include $viewFile;
|
||||
foreach(glob($modulePath . 'ext/view/sendmail.*.html.hook.php') as $hookFile) include $hookFile;
|
||||
$mailContent = ob_get_contents();
|
||||
ob_end_clean();
|
||||
chdir($oldcwd);
|
||||
|
||||
/* Get the sender. */
|
||||
if($objectType == 'story' or $objectType == 'meeting')
|
||||
{
|
||||
$sendUsers = $this->{$objectType}->getToAndCcList($object, $action->action);
|
||||
}
|
||||
elseif($objectType == 'review')
|
||||
{
|
||||
$sendUsers = array($object->auditedBy, '');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sendUsers = $this->{$objectType}->getToAndCcList($object);
|
||||
}
|
||||
|
||||
if(!$sendUsers) return;
|
||||
list($toList, $ccList) = $sendUsers;
|
||||
|
||||
/* Send it. */
|
||||
$this->send($toList, $subject, $mailContent, $ccList);
|
||||
if($this->isError()) error_log(join("\n", $this->getError()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subject.
|
||||
*
|
||||
* @param string $objectType
|
||||
* @param object $object
|
||||
* @param string $title
|
||||
* @param string $actionType
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getSubject($objectType, $object, $title, $actionType)
|
||||
{
|
||||
$suffix = '';
|
||||
$subject = '';
|
||||
$titleType = 'edit';
|
||||
|
||||
if($objectType == 'testtask')
|
||||
{
|
||||
$this->app->loadLang('testtask');
|
||||
|
||||
if($actionType == 'opened') $titleType = 'create';
|
||||
if($actionType == 'closed') $titleType = 'close';
|
||||
|
||||
$subject = sprintf($this->lang->testtask->mail->{$titleType}->title, $this->app->user->realname, $object->id, $object->name);
|
||||
}
|
||||
elseif($objectType == 'doc')
|
||||
{
|
||||
$this->app->loadLang('doc');
|
||||
|
||||
if($actionType == 'created') $titleType = 'create';
|
||||
$subject = sprintf($this->lang->doc->mail->{$titleType}->title, $this->app->user->realname, $object->id, $object->title);
|
||||
}
|
||||
else
|
||||
{
|
||||
if($objectType == 'story' or $objectType == 'bug') $suffix = empty($object->product) ? '' : ' - ' . $this->loadModel('product')->getById($object->product)->name;
|
||||
if($objectType == 'task') $suffix = empty($object->execution) ? '' : ' - ' . $this->loadModel('execution')->getById($object->execution)->name;
|
||||
|
||||
$subject = strtoupper($objectType) . ' #' . $object->id . ' ' . $title . $suffix;
|
||||
}
|
||||
return $subject;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,8 +76,7 @@ class messageModel extends model
|
||||
if(isset($actions[$objectType]) and in_array($actionType, $actions[$objectType]))
|
||||
{
|
||||
$moduleName = $objectType == 'case' ? 'testcase' : $objectType;
|
||||
$this->loadModel($moduleName);
|
||||
if(method_exists($this->$moduleName, 'sendmail')) $this->$moduleName->sendmail($objectID, $actionID);
|
||||
$this->loadModel('mail')->sendmail($objectID, $actionID);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -529,7 +529,7 @@ class my extends control
|
||||
$this->app->loadLang('project');
|
||||
|
||||
$this->app->session->set('programList', $this->app->getURI(true), 'program');
|
||||
$this->app->session->set('projectList', $this->app->getURI(true), 'project');
|
||||
$this->app->session->set('projectList', $this->app->getURI(true), 'my');
|
||||
|
||||
/* Set the pager. */
|
||||
$this->app->loadClass('pager', $static = true);
|
||||
|
||||
@@ -5,8 +5,11 @@ table+table {border-top: 1px solid #eee;}
|
||||
.row td {width: 33%; text-align: left; font-weight: 700;}
|
||||
.main-actions {margin: 5px 0 10px 0; text-align: center;}
|
||||
.user-title {margin: 10px 10px -10px 10px; font-size: 14px; font-weight: bold; padding-left: 10px;}
|
||||
.avatar {display: inline-block; width: 50px; height: 50px; line-height: 50px;}
|
||||
#avatarForm {width: 100%; height: 100%;}
|
||||
.avatar .btn-avatar {padding: 0; vertical-align: revert; border: 0; background-color: auto; color: #fff; font-size: 20px;}
|
||||
.user-name {margin-left: 15px; font-weight: bold;}
|
||||
.user-name, .user-role {line-height: 50px; font-size: 16px; vertical-align: top;}
|
||||
|
||||
#avatarUpload {display: inline-block; width: 50px; height: 50px; position: relative;}
|
||||
#avatarForm {position: absolute; top: 0; left: 0; right: 0; bottom: 0;}
|
||||
#avatarUploadBtn {display: block; position: absolute; top: 0; left: 0; right: 0; bottom: 0;}
|
||||
#avatarUploadBtn {opacity: 0; color: #fff; border-radius: 50%; line-height: 50px;}
|
||||
#avatarUploadBtn:hover {opacity: 1; background-color: rgba(0,0,0,.5);}
|
||||
|
||||
@@ -22,6 +22,8 @@ $(document).ready(function()
|
||||
window.parent.$('#main-avatar, #menu-avatar').html('<img src="' + avatar + '"/>');
|
||||
window.parent.$('#mainContent>.cell>.main-header>.avatar').html('<img src="' + avatar + '"/>');
|
||||
window.parent.$('#mainContent .avatar-' + account).html('<img src="' + avatar + '"/>');
|
||||
|
||||
$('#avatarUploadBtn').tooltip();
|
||||
});
|
||||
|
||||
function uploadAvatar()
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<?php else:?>
|
||||
<form class='main-table' id='projectForm' method='post' data-ride='table' data-checkable='false'>
|
||||
<form id='projectForm' method='post' data-ride='table' data-checkable='false'>
|
||||
<table class='table table-fixed' id='docList'>
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
<div id='mainContent'>
|
||||
<div class='cell'>
|
||||
<div class='main-header text-center'>
|
||||
<span class="avatar avatar bg-secondary avatar-circle">
|
||||
<div id="avatarUpload">
|
||||
<?php echo html::avatar($user, 50); ?>
|
||||
<form method='post' class='form-ajax' action=<?php echo inlink('uploadAvatar');?> id='avatarForm' enctype='multipart/form-data'>
|
||||
<input type="file" name="files" id="files" class="form-control hidden">
|
||||
<?php $avatar = $user->avatar ? html::image($user->avatar) : strtoupper($user->account[0]);?>
|
||||
<?php echo html::a('javascript:void(0);', $avatar, '', "class='btn-avatar' id='avatarUploadBtn' data-placement='right'");?>
|
||||
<?php echo html::a('javascript:void(0);', '<i class="icon icon-pencil icon-2x"></i>', '', "class='btn-avatar' id='avatarUploadBtn' data-toggle='tooltip' data-container='body' data-placement='bottom' title='{$lang->my->uploadAvatar}'");?>
|
||||
</form>
|
||||
</span>
|
||||
</div>
|
||||
<span class='user-name'><?php echo $user->realname;?></span>
|
||||
<span class='user-role'><?php echo zget($lang->user->roleList, $user->role, '');?></span>
|
||||
</div>
|
||||
|
||||
@@ -76,6 +76,7 @@ class pipelineModel extends model
|
||||
->add('private',md5(rand(10,113450)))
|
||||
->add('createdBy', $this->app->user->account)
|
||||
->add('createdDate', helper::now())
|
||||
->trim('token')
|
||||
->skipSpecial('url,token,account,password')
|
||||
->get();
|
||||
if($type == 'gitlab') $pipeline->url = rtrim($pipeline->url, '/');
|
||||
@@ -104,6 +105,7 @@ class pipelineModel extends model
|
||||
$pipeline = fixer::input('post')
|
||||
->add('editedBy', $this->app->user->account)
|
||||
->add('editedDate', helper::now())
|
||||
->trim('token')
|
||||
->skipSpecial('url,token,account,password')
|
||||
->get();
|
||||
|
||||
|
||||
@@ -1278,7 +1278,7 @@ class product extends control
|
||||
$product->closedBugs = (int)$product->closedBugs;
|
||||
$product->bugFixedRate = (($product->unResolved + $product->fixedBugs) == 0 ? 0 : round($product->fixedBugs / ($product->unResolved + $product->fixedBugs), 3) * 100) . '%';
|
||||
$product->program = $product->programName;
|
||||
|
||||
|
||||
/* get rowspan data */
|
||||
if($lastProgram == '' or $product->program != $lastProgram)
|
||||
{
|
||||
|
||||
@@ -117,7 +117,8 @@ $projectIDParam = $isProjectStory ? "projectID=$projectID&" : '';
|
||||
}
|
||||
else
|
||||
{
|
||||
echo html::a($this->createLink($this->app->rawModule, $this->app->rawMethod, $projectIDParam . "productID=$productID&branch=$branch&browseType=$menuBrowseType¶m=0&storyType=$storyType"), "<span class='text'>$menuItem->text</span>" . ($menuItem->name == $this->session->storyBrowseType ? ' <span class="label label-light label-badge">' . $pager->recTotal . '</span>' : ''), '', "id='{$menuItem->name}Tab' class='btn btn-link" . ($this->session->storyBrowseType == $menuItem->name ? ' btn-active-text' : '') . "'");
|
||||
$menuItemName = strtolower($menuItem->name);
|
||||
echo html::a($this->createLink($this->app->rawModule, $this->app->rawMethod, $projectIDParam . "productID=$productID&branch=$branch&browseType=$menuBrowseType¶m=0&storyType=$storyType"), "<span class='text'>$menuItem->text</span>" . ($menuItemName == $this->session->storyBrowseType ? ' <span class="label label-light label-badge">' . $pager->recTotal . '</span>' : ''), '', "id='{$menuItem->name}Tab' class='btn btn-link" . ($this->session->storyBrowseType == $menuItemName ? ' btn-active-text' : '') . "'");
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -67,6 +67,12 @@ class program extends control
|
||||
$this->display();
|
||||
}
|
||||
|
||||
public function kanban()
|
||||
{
|
||||
$this->view->kanbanGroup = $this->program->getKanbanGroup();
|
||||
$this->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Program products list.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
.main-table .table {cursor: default;}
|
||||
.main-table .table td {background: #f5f5f5;}
|
||||
|
||||
.board-item {border: 1px solid #EBEBEB; padding: 5px 10px; cursor: default; border-radius: 2px; background-color: #fff;}
|
||||
.board-item:hover {border-color: #ccc;}
|
||||
.board-item + .board-item {margin-top: 10px;}
|
||||
|
||||
#kanban {overflow-y: auto;}
|
||||
#kanban tbody > tr > td {line-height: 24px;}
|
||||
#kanban tbody > tr > td{border-right: 3px solid #fff; border-bottom: 3px solid #fff;}
|
||||
#kanban thead > tr:first-child > th:not(:first-child) {border-right: 3px solid #fff; border-bottom: 3px solid #fff;}
|
||||
#kanban thead > tr:last-child > th {border: 3px solid #fff;}
|
||||
|
||||
.table-grouped tbody > tr > td:first-child, .table-grouped thead > tr > th:first-child {padding-left: 3px;}
|
||||
@@ -62,6 +62,9 @@ $lang->program->readjustTime = 'Change the program begin&end date.';
|
||||
$lang->program->stakeholderTypeList['inside'] = 'Inside';
|
||||
$lang->program->stakeholderTypeList['outside'] = 'Outside';
|
||||
|
||||
$lang->program->typeList['my'] = 'My Programs';
|
||||
$lang->program->typeList['others'] = 'Others';
|
||||
|
||||
$lang->program->noProgram = 'No program.';
|
||||
$lang->program->showClosed = 'Closed programs.';
|
||||
$lang->program->tips = 'If a parent item set is selected, products under that parent item set can be associated. If no item set is selected, a product with the same name as the item is created by default and associated with that item.';
|
||||
|
||||
@@ -74,6 +74,9 @@ $lang->program->isStakeholderKey = 'Key stakeholder';
|
||||
$lang->program->stakeholderTypeList['inside'] = 'Inside';
|
||||
$lang->program->stakeholderTypeList['outside'] = 'Outside';
|
||||
|
||||
$lang->program->typeList['my'] = 'My Programs';
|
||||
$lang->program->typeList['others'] = 'Others';
|
||||
|
||||
$lang->program->noProgram = 'No program.';
|
||||
$lang->program->showClosed = 'Closed programs.';
|
||||
$lang->program->tips = 'If a parent item set is selected, products under that parent item set can be associated. If no item set is selected, a product with the same name as the item is created by default and associated with that item.';
|
||||
|
||||
@@ -62,6 +62,9 @@ $lang->program->readjustTime = 'Change the program begin&end date.';
|
||||
$lang->program->stakeholderTypeList['inside'] = 'Inside';
|
||||
$lang->program->stakeholderTypeList['outside'] = 'Outside';
|
||||
|
||||
$lang->program->typeList['my'] = 'My Programs';
|
||||
$lang->program->typeList['others'] = 'Others';
|
||||
|
||||
$lang->program->noProgram = 'No program.';
|
||||
$lang->program->showClosed = 'Closed programs.';
|
||||
$lang->program->tips = 'If a parent item set is selected, products under that parent item set can be associated. If no item set is selected, a product with the same name as the item is created by default and associated with that item.';
|
||||
|
||||
@@ -62,6 +62,9 @@ $lang->program->readjustTime = 'Change the program begin&end date.';
|
||||
$lang->program->stakeholderTypeList['inside'] = 'Inside';
|
||||
$lang->program->stakeholderTypeList['outside'] = 'Outside';
|
||||
|
||||
$lang->program->typeList['my'] = 'My Programs';
|
||||
$lang->program->typeList['others'] = 'Others';
|
||||
|
||||
$lang->program->noProgram = 'No program.';
|
||||
$lang->program->showClosed = 'Closed programs.';
|
||||
$lang->program->tips = 'If a parent item set is selected, products under that parent item set can be associated. If no item set is selected, a product with the same name as the item is created by default and associated with that item.';
|
||||
|
||||
@@ -115,3 +115,16 @@ $lang->program->statusList['suspended'] = '已挂起';
|
||||
$lang->program->statusList['closed'] = '已关闭';
|
||||
|
||||
$lang->program->featureBar['all'] = '所有';
|
||||
|
||||
$lang->program->kanban = new stdclass();
|
||||
$lang->program->kanban->typeList['my'] = '我参与的项目集';
|
||||
$lang->program->kanban->typeList['others'] = '其他项目集';
|
||||
|
||||
$lang->program->kanban->activeProducts = '未关闭的产品';
|
||||
$lang->program->kanban->activePlans = '未过期的计划';
|
||||
$lang->program->kanban->waitProjects = '未开始的项目';
|
||||
$lang->program->kanban->doingProjects = '进行中的项目';
|
||||
$lang->program->kanban->doingExecutions = '进行中的执行';
|
||||
$lang->program->kanban->normalReleases = '正常的发布';
|
||||
|
||||
$lang->program->kanban->laneColorList = array('#32C5FF', '#006AF1', '#9D28B2', '#FF8F26', '#FFC20E', '#00A78E', '#7FBB00', '#424BAC', '#C0E9FF', '#EC2761');
|
||||
|
||||
+266
-2
@@ -96,7 +96,7 @@ class programModel extends model
|
||||
*/
|
||||
public function getByID($programID = 0)
|
||||
{
|
||||
$program = $this->dao->select('*')->from(TABLE_PROGRAM)->where('id')->eq($programID)->andWhere('`type`')->eq('program')->fetch();
|
||||
$program = $this->dao->select('*')->from(TABLE_PROGRAM)->where('id')->eq($programID)->fetch();
|
||||
$program = $this->loadModel('file')->replaceImgURL($program, 'desc');
|
||||
return $program;
|
||||
}
|
||||
@@ -143,7 +143,271 @@ class programModel extends model
|
||||
->fetchAll('id');
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Get kanban group data.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function getKanbanGroup()
|
||||
{
|
||||
$kanbanGroup = array();
|
||||
$kanbanGroup['my'] = array();
|
||||
$kanbanGroup['others'] = array();
|
||||
|
||||
/* Get all prived programs. */
|
||||
$programs = $this->dao->select('id, name')->from(TABLE_PROGRAM)
|
||||
->where('type')->eq('program')
|
||||
->andWhere('deleted')->eq(0)
|
||||
->andWhere('grade')->eq(1)
|
||||
->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->programs)->fi()
|
||||
->andWhere('status')->ne('closed')
|
||||
->fetchAll('id');
|
||||
|
||||
$involvedPrograms = $this->getInvolvedPrograms($this->app->user->account);
|
||||
|
||||
/* Get all products under programs. */
|
||||
$productGroup = $this->dao->select('id, program, name')->from(TABLE_PRODUCT)
|
||||
->where('deleted')->eq(0)
|
||||
->andWhere('program')->in(array_keys($programs))
|
||||
->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->products)->fi()
|
||||
->andWhere('status')->ne('closed')
|
||||
->fetchGroup('program', 'id');
|
||||
|
||||
$productPairs = array();
|
||||
foreach($productGroup as $programID => $products)
|
||||
{
|
||||
foreach($products as $productID => $product) $productPairs[$productID] = $productID;
|
||||
}
|
||||
|
||||
/* Get all plans under products. */
|
||||
$plans = $this->dao->select('id, product, title')->from(TABLE_PRODUCTPLAN)
|
||||
->where('deleted')->eq(0)
|
||||
->andWhere('product')->in($productPairs)
|
||||
->andWhere('end')->gt(helper::today())
|
||||
->fetchGroup('product');
|
||||
|
||||
/* Get all products linked projects and executions. */
|
||||
$projectGroup = $this->dao->select('t1.product, t2.id, t2.name, t2.status')->from(TABLE_PROJECTPRODUCT)->alias('t1')
|
||||
->leftJoin(TABLE_PROJECT)->alias('t2')
|
||||
->on('t1.project = t2.id')
|
||||
->where('t2.deleted')->eq(0)
|
||||
->andWhere('t1.product')->in($productPairs)
|
||||
->andWhere('t2.status')->ne('closed')
|
||||
->andWhere('t2.type')->eq('project')
|
||||
->fetchGroup('product');
|
||||
|
||||
$projectPairs = array();
|
||||
foreach($projectGroup as $projects)
|
||||
{
|
||||
foreach($projects as $project) $projectPairs[$project->id] = $project->id;
|
||||
}
|
||||
|
||||
$tasks = $this->dao->select('id, project, estimate, consumed, `left`, status, closedReason, execution')
|
||||
->from(TABLE_TASK)
|
||||
->where('project')->in($projectPairs)
|
||||
->andWhere('parent')->lt(1)
|
||||
->andWhere('deleted')->eq(0)
|
||||
->fetchGroup('project', 'id');
|
||||
|
||||
$projectHours = $this->computeProgress($tasks);
|
||||
|
||||
$releases = $this->dao->select('product, id, name')->from(TABLE_RELEASE)
|
||||
->where('product')->in($productPairs)
|
||||
->andWhere('deleted')->eq(0)
|
||||
->andWhere('status')->eq('normal')
|
||||
->fetchGroup('product');
|
||||
|
||||
$doingExecutions = $this->dao->select('id, project, name')->from(TABLE_EXECUTION)
|
||||
->where('type')->in('sprint,stage')
|
||||
->andWhere('status')->eq('doing')
|
||||
->andWhere('deleted')->eq(0)
|
||||
->orderBy('id_asc')
|
||||
->fetchAll('project');
|
||||
|
||||
$executionPairs = array();
|
||||
foreach($doingExecutions as $execution) $executionPairs[$execution->id] = $execution->id;
|
||||
|
||||
$tasks = $this->dao->select('id, project, estimate, consumed, `left`, status, closedReason, execution')
|
||||
->from(TABLE_TASK)
|
||||
->where('execution')->in($executionPairs)
|
||||
->andWhere('parent')->lt(1)
|
||||
->andWhere('deleted')->eq(0)
|
||||
->fetchGroup('execution', 'id');
|
||||
|
||||
$executionHours = $this->computeProgress($tasks);
|
||||
|
||||
foreach($productGroup as $programID => $products)
|
||||
{
|
||||
foreach($products as $productID => $product)
|
||||
{
|
||||
$product->plans = zget($plans, $productID, array());
|
||||
$product->releases = zget($releases, $productID, array());
|
||||
$projects = zget($projectGroup, $productID, array());
|
||||
foreach($projects as $project)
|
||||
{
|
||||
$status = $project->status == 'wait' ? 'wait' : 'doing';
|
||||
$execution = zget($doingExecutions, $project->id, array());
|
||||
if(!empty($execution)) $execution->hours = zget($executionHours, $execution->id, array());
|
||||
|
||||
$project->execution = $execution;
|
||||
$project->hours = zget($projectHours, $project->id, array());
|
||||
$product->projects[$status][] = $project;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//区分出其他项目集
|
||||
foreach($programs as $programID => $program)
|
||||
{
|
||||
$program->products = zget($productGroup, $programID, array());
|
||||
|
||||
if(in_array($programID, $involvedPrograms))
|
||||
{
|
||||
$kanbanGroup['my'][$programID] = $program;
|
||||
}
|
||||
else
|
||||
{
|
||||
$kanbanGroup['others'][$programID] = $program;
|
||||
}
|
||||
}
|
||||
|
||||
return $kanbanGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get involved programs by user.
|
||||
*
|
||||
* @param string $account
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function getInvolvedPrograms($account)
|
||||
{
|
||||
$involvedPrograms = array();
|
||||
|
||||
/* All involves in program table. */
|
||||
$objects = $this->dao->select('id, type, project')->from(TABLE_PROGRAM)
|
||||
->where('deleted')->eq(0)
|
||||
->andWhere("(openedBy = '$account' or PM = '$account')")
|
||||
->fetchAll('id');
|
||||
|
||||
foreach($objects as $id => $object)
|
||||
{
|
||||
if($object->type == 'program') $involvedPrograms[$id] = $id;
|
||||
if($object->type == 'project')
|
||||
{
|
||||
$programID = $this->getTopByID($id);
|
||||
$involvedPrograms[$programID] = $programID;
|
||||
}
|
||||
if($object->type == 'sprint' || $object->type == 'stage')
|
||||
{
|
||||
$programID = $this->getTopByID($object->project);
|
||||
$involvedPrograms[$programID] = $programID;
|
||||
}
|
||||
}
|
||||
|
||||
/* All involves in stakeholder table. */
|
||||
$stakeholders = $this->dao->select('t1.objectID, t2.type')->from(TABLE_STAKEHOLDER)->alias('t1')
|
||||
->leftJoin(TABLE_PROJECT)->alias('t2')
|
||||
->on('t1.objectID = t2.id')
|
||||
->where('t1.objectType')->in("program,project")
|
||||
->andWhere('t1.user')->eq($account)
|
||||
->fetchAll('objectID');
|
||||
|
||||
foreach($stakeholders as $objectID => $object)
|
||||
{
|
||||
if($object->type == 'program')
|
||||
{
|
||||
$involvedPrograms[$objectID] = $objectID;
|
||||
}
|
||||
if($object->type == 'project')
|
||||
{
|
||||
$programID = $this->getTopByID($objectID);
|
||||
$involvedPrograms[$programID] = $programID;
|
||||
}
|
||||
}
|
||||
|
||||
/* All involves in team table. */
|
||||
$teams = $this->dao->select('t1.root, t2.project, t2.type')->from(TABLE_TEAM)->alias('t1')
|
||||
->leftJoin(TABLE_PROJECT)->alias('t2')
|
||||
->on('t1.root = t2.id')
|
||||
->where('t1.account')->eq($account)
|
||||
->andWhere('t1.type')->in('project,execution')
|
||||
->fetchAll('root');
|
||||
|
||||
foreach($teams as $objectID => $object)
|
||||
{
|
||||
if($object->type == 'project')
|
||||
{
|
||||
$programID = $this->getTopByID($objectID);
|
||||
$involvedPrograms[$programID] = $programID;
|
||||
}
|
||||
if($object->type == 'execution')
|
||||
{
|
||||
$programID = $this->getTopByID($object->project);
|
||||
$involvedPrograms[$programID] = $programID;
|
||||
}
|
||||
}
|
||||
|
||||
/* All involves in products table. */
|
||||
$products = $this->dao->select('id, program, createdBy, PO, QD, RD')->from(TABLE_PRODUCT)
|
||||
->where('deleted')->eq(0)
|
||||
->andWhere("(createdBy = '$account' or PO = '$account' or QD = '$account' or RD = '$account')")
|
||||
->fetchAll('id');
|
||||
|
||||
foreach($products as $id => $product) $involvedPrograms[$product->program] = $product->program;
|
||||
|
||||
/* Check priv. */
|
||||
$involvedPrograms = $this->dao->select('id')->from(TABLE_PROGRAM)
|
||||
->where('deleted')->eq(0)
|
||||
->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->programs)->fi()
|
||||
->andWhere('id')->in($involvedPrograms)
|
||||
->andWhere('grade')->eq(1)
|
||||
->fetchPairs();
|
||||
|
||||
return $involvedPrograms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute progress for project or execution.
|
||||
*
|
||||
* @param array $tasks
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function computeProgress($tasks)
|
||||
{
|
||||
$hours = array();
|
||||
foreach($tasks as $projectID => $projectTasks)
|
||||
{
|
||||
$hour = new stdclass();
|
||||
$hour->totalConsumed = 0;
|
||||
$hour->totalEstimate = 0;
|
||||
$hour->totalLeft = 0;
|
||||
|
||||
foreach($projectTasks as $task)
|
||||
{
|
||||
$hour->totalConsumed += $task->consumed;
|
||||
$hour->totalEstimate += $task->estimate;
|
||||
if($task->status != 'cancel' and $task->status != 'closed') $hour->totalLeft += $task->left;
|
||||
}
|
||||
$hours[$projectID] = $hour;
|
||||
}
|
||||
|
||||
foreach($hours as $hour)
|
||||
{
|
||||
$hour->totalEstimate = round($hour->totalEstimate, 1) ;
|
||||
$hour->totalConsumed = round($hour->totalConsumed, 1);
|
||||
$hour->totalLeft = round($hour->totalLeft, 1);
|
||||
$hour->totalReal = $hour->totalConsumed + $hour->totalLeft;
|
||||
$hour->progress = $hour->totalReal ? round($hour->totalConsumed / $hour->totalReal, 2) * 100 : 0;
|
||||
}
|
||||
|
||||
return $hours;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get project list data.
|
||||
*
|
||||
* @param int $programID
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<th class='text-right w-100px'><?php common::printOrderLink('budget', $orderBy, $vars, $lang->project->budget);?></th>
|
||||
<th class='w-100px'><?php common::printOrderLink('begin', $orderBy, $vars, $lang->project->begin);?></th>
|
||||
<th class='w-100px'><?php common::printOrderLink('end', $orderBy, $vars, $lang->project->end);?></th>
|
||||
<th class='w-70px'><?php echo $lang->project->progress;?></th>
|
||||
<th class='w-60px'><?php echo $lang->project->progress;?></th>
|
||||
<th class='text-center w-180px'><?php echo $lang->actions;?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -62,9 +62,7 @@
|
||||
<td class='c-status'><span class="status-program status-<?php echo $program->status?>"><?php echo zget($lang->project->statusList, $program->status, '');?></span></td>
|
||||
<td>
|
||||
<?php if(!empty($program->PM)):?>
|
||||
<div class="avatar bg-secondary avatar-circle avatar-<?php echo $program->PM;?>">
|
||||
<?php echo !empty($usersAvatar[$program->PM]) ? html::image($usersAvatar[$program->PM]) : strtoupper($program->PM[0]);?>
|
||||
</div>
|
||||
<?php echo html::smallAvatar(array('avatar' => $usersAvatar[$program->PM], 'account' => $program->PM)); ?>
|
||||
<?php $userID = isset($PMList[$program->PM]) ? $PMList[$program->PM]->id : '';?>
|
||||
<?php $userName = zget($users, $program->PM);?>
|
||||
<?php echo html::a($this->createLink('user', 'profile', "userID=$userID", '', true), $userName, '', "title='{$userName}' data-toggle='modal' data-type='iframe' data-width='600'");?>
|
||||
@@ -76,7 +74,7 @@
|
||||
<td><?php echo $program->end == LONG_TIME ? $lang->program->longTime : $program->end;?></td>
|
||||
<td>
|
||||
<?php if(isset($progressList[$program->id])):?>
|
||||
<div class='progress-pie' data-doughnut-size='90' data-color='#3CB371' data-value='<?php echo $progressList[$program->id]?>' data-width='24' data-height='24' data-back-color='#e8edf3'>
|
||||
<div class='progress-pie' data-doughnut-size='85' data-color='#00DA88' data-value='<?php echo $progressList[$program->id]?>' data-width='26' data-height='26' data-back-color='#e8edf3'>
|
||||
<div class='progress-info'><?php echo $progressList[$program->id];?></div>
|
||||
</div>
|
||||
<?php endif;?>
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
/**
|
||||
* The html template file of kanban method of program module of ZenTaoPMS.
|
||||
*
|
||||
* @copyright Copyright 2009-2015 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
|
||||
* @license ZPL (http://zpl.pub/page/zplv12.html)
|
||||
* @author Guangming Sun<sunguangming@cnezsoft.com>
|
||||
* @package ZenTaoPMS
|
||||
* @version $Id
|
||||
*/
|
||||
?>
|
||||
<?php include '../../common/view/header.html.php';?>
|
||||
<div id="kanban" class="main-table fade auto-fade-in" data-ride="table" data-checkable="false" data-group="true">
|
||||
<?php foreach($kanbanGroup as $type => $programGroup):?>
|
||||
<?php $colorIndex = 0;?>
|
||||
<div class="cell">
|
||||
<div class='detail'>
|
||||
<div class='detail-title'><?php echo $lang->program->kanban->typeList[$type];?></div>
|
||||
<div class='detail-content'>
|
||||
<table class="table no-margin table-grouped text-center" style='background: #f5f5f5;'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan='2' class='w-20px' style='background: #32C5FF; border-bottom: none'></th>
|
||||
<th rowspan='2'><?php echo $lang->program->kanban->activeProducts;?></th>
|
||||
<th rowspan='2'><?php echo $lang->program->kanban->activePlans;?></th>
|
||||
<th rowspan='2'><?php echo $lang->program->kanban->waitProjects;?></th>
|
||||
<th colspan='2'><?php echo $lang->program->statusList['doing'];?></th>
|
||||
<th rowspan='2'><?php echo $lang->program->kanban->normalReleases;?></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?php echo $lang->program->kanban->doingProjects;?></th>
|
||||
<th><?php echo $lang->program->kanban->doingExecutions;?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($programGroup as $programID => $program):?>
|
||||
<tr>
|
||||
<td style='background: <?php echo $lang->program->kanban->laneColorList[$colorIndex];?>; color: #fff; border-right: none;' rowspan='<?php echo count($program->products);?>'><?php echo $program->name;?></td>
|
||||
<?php $i = 0;?>
|
||||
<?php if(!empty($program->products)):?>
|
||||
<?php foreach($program->products as $productID => $product):?>
|
||||
<?php if($i != 0) echo '<tr>';?>
|
||||
<td><?php echo $product->name;?></td>
|
||||
<td>
|
||||
<?php foreach($product->plans as $planID => $plan):?>
|
||||
<div class='board-item'>
|
||||
<div class='table-row'>
|
||||
<div class='table-col'>
|
||||
<?php echo html::a($this->createLink('productplan', 'view', "planID=$plan->id"), $plan->title);?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach;?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if(isset($product->projects['wait'])):?>
|
||||
<?php foreach($product->projects['wait'] as $projectID => $project):?>
|
||||
<div class='board-item'>
|
||||
<div class='table-row'>
|
||||
<div class='table-col'>
|
||||
<?php echo html::a($this->createLink('project', 'view', "projectID=$projectID"), $project->name);?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach;?>
|
||||
<?php endif;?>
|
||||
</td>
|
||||
<td class='doing-project'>
|
||||
<?php if(isset($product->projects['doing'])):?>
|
||||
<?php foreach($product->projects['doing'] as $projectID => $project):?>
|
||||
<div class='board-item'>
|
||||
<div class='table-row'>
|
||||
<div class='table-col'>
|
||||
<?php echo html::a($this->createLink('project', 'view', "projectID=$projectID"), $project->name);?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach;?>
|
||||
<?php endif;?>
|
||||
</td>
|
||||
<td class='doing-execution'>
|
||||
<?php if(isset($product->projects['doing'])):?>
|
||||
<?php foreach($product->projects['doing'] as $projectID => $project):?>
|
||||
<div class='board-item'>
|
||||
<div class='table-row'>
|
||||
<div class='table-col'>
|
||||
<?php if(!empty($project->execution)):?>
|
||||
<?php echo html::a($this->createLink('execution', 'view', "executionID={$project->execution->id}"), $project->execution->name);?>
|
||||
<?php endif;?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach;?>
|
||||
<?php endif;?>
|
||||
</td>
|
||||
<td>
|
||||
<?php foreach($product->releases as $releaseID => $release):?>
|
||||
<div class='board-item'>
|
||||
<div class='table-row'>
|
||||
<div class='table-col'>
|
||||
<?php echo html::a($this->createLink('release', 'view', "releaseID=$release->id"), $release->name);?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach;?>
|
||||
</td>
|
||||
<?php if($i != 0) echo '</tr>';?>
|
||||
<?php $i ++;?>
|
||||
<?php endforeach;?>
|
||||
<?php else:?>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<?php endif;?>
|
||||
</tr>
|
||||
<?php $colorIndex ++;?>
|
||||
<?php if($colorIndex > 9) $colorIndex = 0;?>
|
||||
<?php endforeach;?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach;?>
|
||||
</div>
|
||||
<?php include '../../common/view/footer.html.php';?>
|
||||
@@ -760,7 +760,7 @@ class projectModel extends model
|
||||
$this->dao->insert(TABLE_PROJECT)->data($project)
|
||||
->autoCheck()
|
||||
->batchcheck($requiredFields, 'notempty')
|
||||
->check('name', 'unique', "type='project'")
|
||||
->check('name', 'unique', "type='project' AND deleted='0'")
|
||||
->exec();
|
||||
|
||||
/* Add the creater to the team. */
|
||||
@@ -951,7 +951,7 @@ class projectModel extends model
|
||||
->checkIF($project->begin != '', 'begin', 'date')
|
||||
->checkIF($project->end != '', 'end', 'date')
|
||||
->checkIF($project->end != '', 'end', 'gt', $project->begin)
|
||||
->check('name', 'unique', "id != $projectID AND type='project'")
|
||||
->check('name', 'unique', "id != $projectID AND type='project' AND deleted='0'")
|
||||
->where('id')->eq($projectID)
|
||||
->exec();
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#cards .project-infos > span + span {margin-left: 15px;}
|
||||
#cards .project-infos > .budget {max-width: 75px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
|
||||
#cards .project-detail {position: absolute; top: 75px; left: 16px; right: 16px; font-size: 12px;}
|
||||
#cards .project-footer {position: absolute; bottom: 5px;}
|
||||
#cards .project-footer {position: absolute; bottom: 10px; right: 10px; left: 15px;}
|
||||
#cards .pager {margin: 0; float: right;}
|
||||
#cards .pager .btn {border: none}
|
||||
#cards .panel .label-wait {background: #EFEFEF !important; color: #838A9D;}
|
||||
@@ -40,16 +40,18 @@
|
||||
#cards .project-infos .text-red {color: #F85A40 !important;}
|
||||
#cards .project-detail .progress-pie {width: 24px; position: absolute; top: 18px;}
|
||||
#cards .project-detail .leftTasks, .totalLeft {display:block; margin-top: 8px;}
|
||||
#cards .project-members {float: left;}
|
||||
#cards .project-members .avatar {display: inline-block; width: 25px; height: 25px; line-height: 25px; margin-right: 1px;}
|
||||
#cards .project-members a:not(:first-child) {margin-left: -5px;}
|
||||
#cards .totalMembers{display: inline-block; margin-left: 6px; position: absolute; bottom: 8px;}
|
||||
#cards .project-actions {float: right;}
|
||||
#cards .project-actions .menu-actions {padding-right: 20px;}
|
||||
#cards .project-actions ul {min-width: 35px; padding: 5px 6px;}
|
||||
#cards .project-actions .dropdown-hover:hover>.dropdown-menu ul,
|
||||
#cards .project-actions .open>.dropdown-menu ul {display: flex;}
|
||||
#cards .menu-actions .btn.btn-action {margin-bottom: -3px !important; margin-right: 5px;}
|
||||
#cards .project-members {float: left; height: 24px; line-height: 24px;}
|
||||
#cards .project-members > a {display: inline-block; height: 24px;}
|
||||
#cards .project-members > a + a {margin-left: -5px;}
|
||||
#cards .project-members > a > .avatar {display: inline-block; width: 24px; height: 24px; line-height: 24px; margin-right: 1px;}
|
||||
#cards .project-members > span {display: inline-block; color: transparent; width: 2px; height: 2px; background-color: #8990a2; position: relative; border-radius: 50%; top: 3px; margin: 0 3px;}
|
||||
#cards .project-members > span:before,
|
||||
#cards .project-members > span:after {content: ''; display: block; position: absolute; width: 2px; height: 2px; background-color: #8990a2; top: 0; border-radius: 50%}
|
||||
#cards .project-members > span:before {left: -4px;}
|
||||
#cards .project-members > span:after {right: -4px;}
|
||||
#cards .project-members-total {display: inline-block; margin-left: 6px; position: relative; top: 3px}
|
||||
#cards .project-actions {position: absolute; right: -8px; bottom: -5px; white-space: nowrap;}
|
||||
#cards .project-actions .dropdown-menu {padding: 5px 6px; top: -5px; right: 30px;}
|
||||
#cards .icon-ellipsis-v {font-size: 13px;}
|
||||
#cards .teamTitle {margin-bottom: 30px;}
|
||||
</style>
|
||||
@@ -138,41 +140,39 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='project-footer table-row'>
|
||||
<div class='project-footer'>
|
||||
<?php $titleClass = ($project->teamCount == 0 and !$canActions) ? 'teamTitle' : '';?>
|
||||
<?php $count = 0;?>
|
||||
<div class="<?php echo $titleClass?>"><?php echo $lang->project->teamMember;?></div>
|
||||
<?php if(!empty($project->teamMembers)):?>
|
||||
<div class='project-members table-col'>
|
||||
<?php foreach($project->teamMembers as $member):?>
|
||||
<?php
|
||||
if($count > 2) continue;
|
||||
if(!isset($users[$member]))
|
||||
{
|
||||
$project->teamCount --;
|
||||
continue;
|
||||
}
|
||||
$count ++;
|
||||
?>
|
||||
<a href='<?php echo helper::createLink('project', 'team', "projectID=$projectID");?>' title="<?php echo $users[$member];?>">
|
||||
<div class="avatar bg-secondary avatar-circle avatar-<?php echo $member;?>">
|
||||
<?php echo !empty($usersAvatar[$member]) ? html::image(zget($usersAvatar, $member)) : strtoupper($member[0]);?>
|
||||
</div>
|
||||
</a>
|
||||
<?php endforeach;?>
|
||||
<?php if($project->teamCount > 3):?>
|
||||
<?php echo '...';?>
|
||||
<a href='<?php echo helper::createLink('project', 'team', "projectID=$projectID");?>' title="<?php echo $users[$member];?>">
|
||||
<div class="avatar bg-secondary avatar-circle avatar-<?php echo $member;?>">
|
||||
<?php echo !empty($usersAvatar[end($project->teamMembers)]) ? html::image(zget($usersAvatar, end($project->teamMembers))) : strtoupper($member[0]);?>
|
||||
</div>
|
||||
</a>
|
||||
<div class="clearfix">
|
||||
<?php if(!empty($project->teamMembers)):?>
|
||||
<div class='project-members pull-left'>
|
||||
<?php foreach($project->teamMembers as $member):?>
|
||||
<?php
|
||||
if($count > 2) continue;
|
||||
if(!isset($users[$member]))
|
||||
{
|
||||
$project->teamCount --;
|
||||
continue;
|
||||
}
|
||||
$count ++;
|
||||
?>
|
||||
<a href='<?php echo helper::createLink('project', 'team', "projectID=$projectID");?>' title="<?php echo $users[$member];?>">
|
||||
<?php echo html::smallAvatar(array('avatar' => $usersAvatar[$member], 'account' => $member)); ?>
|
||||
</a>
|
||||
<?php endforeach;?>
|
||||
<?php if($project->teamCount > 3):?>
|
||||
<?php echo '<span>…</span>';?>
|
||||
<a href='<?php echo helper::createLink('project', 'team', "projectID=$projectID");?>' title="<?php echo $users[$member];?>">
|
||||
<?php echo html::smallAvatar(array('avatar' => $usersAvatar[end($project->teamMembers)], 'account' => $member)); ?>
|
||||
</a>
|
||||
<?php endif;?>
|
||||
</div>
|
||||
<?php endif;?>
|
||||
<div class='project-members-total pull-left'><?php echo html::a(helper::createLink('project', 'team', "projectID=$projectID"), sprintf($lang->project->teamSumCount, $project->teamCount));?></div>
|
||||
</div>
|
||||
<?php endif;?>
|
||||
<span class='totalMembers'><?php echo html::a(helper::createLink('project', 'team', "projectID=$projectID"), sprintf($lang->project->teamSumCount, $project->teamCount));?></span>
|
||||
<div class='project-actions table-col'>
|
||||
<div class='menu-actions'>
|
||||
<div class='project-actions'>
|
||||
<div class='dropdown'>
|
||||
<?php if($canActions):?>
|
||||
<?php echo html::a('javascript:;', "<i class='icon icon-ellipsis-v'></i>", '', "data-toggle='dropdown' class='btn btn-link'");?>
|
||||
<ul class='dropdown-menu pull-right'>
|
||||
|
||||
@@ -18,7 +18,9 @@ $lang->projectstory->batchUnlinkTip = 'Other requirements are removed. The follo
|
||||
|
||||
global $app;
|
||||
$app->loadLang('product');
|
||||
$lang->projectstory->featureBar['story']['allstory'] = $lang->product->allStory;
|
||||
$lang->projectstory->featureBar['story']['unclosed'] = $lang->product->unclosed;
|
||||
$lang->projectstory->featureBar['story']['changed'] = $lang->product->changedStory;
|
||||
$lang->projectstory->featureBar['story']['closed'] = $lang->product->closedStory;
|
||||
$lang->projectstory->featureBar['story']['allstory'] = $lang->product->allStory;
|
||||
$lang->projectstory->featureBar['story']['unclosed'] = $lang->product->unclosed;
|
||||
$lang->projectstory->featureBar['story']['changed'] = $lang->product->changedStory;
|
||||
$lang->projectstory->featureBar['story']['closed'] = $lang->product->closedStory;
|
||||
$lang->projectstory->featureBar['story']['linkedExecution'] = 'Linked ' . $lang->execution->common;
|
||||
$lang->projectstory->featureBar['story']['unlinkedExecution'] = 'Unlinked ' . $lang->execution->common;
|
||||
|
||||
@@ -18,7 +18,9 @@ $lang->projectstory->batchUnlinkTip = '其他需求已经移除,如下需求
|
||||
|
||||
global $app;
|
||||
$app->loadLang('product');
|
||||
$lang->projectstory->featureBar['story']['allstory'] = $lang->product->allStory;
|
||||
$lang->projectstory->featureBar['story']['unclosed'] = $lang->product->unclosed;
|
||||
$lang->projectstory->featureBar['story']['changed'] = $lang->product->changedStory;
|
||||
$lang->projectstory->featureBar['story']['closed'] = $lang->product->closedStory;
|
||||
$lang->projectstory->featureBar['story']['allstory'] = $lang->product->allStory;
|
||||
$lang->projectstory->featureBar['story']['unclosed'] = $lang->product->unclosed;
|
||||
$lang->projectstory->featureBar['story']['changed'] = $lang->product->changedStory;
|
||||
$lang->projectstory->featureBar['story']['closed'] = $lang->product->closedStory;
|
||||
$lang->projectstory->featureBar['story']['linkedExecution'] = '已关联' . $lang->execution->common;
|
||||
$lang->projectstory->featureBar['story']['unlinkedExecution'] = '未关联' . $lang->execution->common;
|
||||
|
||||
@@ -477,53 +477,6 @@ class releaseModel extends model
|
||||
return dao::isError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send mail.
|
||||
*
|
||||
* @param int $releaseID
|
||||
* @param int $actionID
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function sendmail($releaseID, $actionID)
|
||||
{
|
||||
$this->loadModel('mail');
|
||||
$release = $this->getByID($releaseID);
|
||||
$users = $this->loadModel('user')->getPairs('noletter');
|
||||
|
||||
/* Get action info. */
|
||||
$action = $this->loadModel('action')->getById($actionID);
|
||||
$history = $this->action->getHistory($actionID);
|
||||
$action->history = isset($history[$actionID]) ? $history[$actionID] : array();
|
||||
$action->appendLink = '';
|
||||
|
||||
/* Get mail content. */
|
||||
$modulePath = $this->app->getModulePath($appName = '', 'release');
|
||||
$oldcwd = getcwd();
|
||||
$viewFile = $modulePath . 'view/sendmail.html.php';
|
||||
chdir($modulePath . 'view');
|
||||
if(file_exists($modulePath . 'ext/view/sendmail.html.php'))
|
||||
{
|
||||
$viewFile = $modulePath . 'ext/view/sendmail.html.php';
|
||||
chdir($modulePath . 'ext/view');
|
||||
}
|
||||
ob_start();
|
||||
include $viewFile;
|
||||
foreach(glob($modulePath . 'ext/view/sendmail.*.html.hook.php') as $hookFile) include $hookFile;
|
||||
$mailContent = ob_get_contents();
|
||||
ob_end_clean();
|
||||
chdir($oldcwd);
|
||||
|
||||
$sendUsers = $this->getToAndCcList($release);
|
||||
if(!$sendUsers) return;
|
||||
list($toList, $ccList) = $sendUsers;
|
||||
$subject = 'RELEASE #' . $release->id . ' ' . $release->name;
|
||||
|
||||
/* Send it. */
|
||||
$this->mail->send($toList, $subject, $mailContent, $ccList);
|
||||
if($this->mail->isError()) error_log(join("\n", $this->mail->getError()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get toList and ccList.
|
||||
*
|
||||
@@ -543,7 +496,7 @@ class releaseModel extends model
|
||||
|
||||
foreach($notifyPersons as $account)
|
||||
{
|
||||
if(strpos($ccList, ",{$account},") === false) $ccList .= $account . ',';
|
||||
if(strpos($ccList, ",{$account},") === false) $ccList .= ",$account,";
|
||||
}
|
||||
|
||||
$ccList = trim($ccList, ',');
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* @link http://www.zentao.net
|
||||
*/
|
||||
?>
|
||||
<?php $mailTitle = 'RELEASE #' . $release->id . ' ' . $release->name;?>
|
||||
<?php $mailTitle = 'RELEASE #' . $object->id . ' ' . $object->name;?>
|
||||
<?php $module = $this->app->openApp == 'product' ? 'release' : 'projectrelease';?>
|
||||
<?php include $this->app->getModuleRoot() . 'common/view/mail.header.html.php';?>
|
||||
<tr>
|
||||
@@ -18,7 +18,7 @@
|
||||
<table cellpadding='0' cellspacing='0' width='600' style='border: none; border-collapse: collapse;'>
|
||||
<tr>
|
||||
<td style='padding: 10px; background-color: #F8FAFE; border: none; font-size: 14px; font-weight: 500; border-bottom: 1px solid #e5e5e5;'>
|
||||
<?php echo html::a(zget($this->config->mail, 'domain', common::getSysURL()) . helper::createLink($module, 'view', "releaseID=$release->id", 'html'), $mailTitle, '', "text-decoration: underline;'");?>
|
||||
<?php echo html::a(zget($this->config->mail, 'domain', common::getSysURL()) . helper::createLink($module, 'view', "releaseID=$object->id", 'html'), $mailTitle, '', "text-decoration: underline;'");?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -28,7 +28,7 @@
|
||||
<td style='padding: 10px; border: none;'>
|
||||
<fieldset style='border: 1px solid #e5e5e5'>
|
||||
<legend style='color: #114f8e'><?php echo $this->lang->release->desc;?></legend>
|
||||
<div style='padding:5px;'><?php echo $release->desc;?></div>
|
||||
<div style='padding:5px;'><?php echo $object->desc;?></div>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
+10
-70
@@ -2581,6 +2581,14 @@ class storyModel extends model
|
||||
$unclosedStatus = $this->lang->story->statusList;
|
||||
unset($unclosedStatus['closed']);
|
||||
|
||||
/* Get story id list of linked executions. */
|
||||
$storyIdList = array();
|
||||
if($type == 'linkedexecution' or $type == 'unlinkedexecution')
|
||||
{
|
||||
$executions = $this->loadModel('execution')->getPairs($executionID);
|
||||
$storyIdList = $this->dao->select('story')->from(TABLE_PROJECTSTORY)->where('project')->in(array_keys($executions))->fetchPairs();
|
||||
}
|
||||
|
||||
$stories = $this->dao->select('distinct t1.*, t2.*,t3.branch as productBranch,t4.type as productType,t2.version as version')->from(TABLE_PROJECTSTORY)->alias('t1')
|
||||
->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id')
|
||||
->leftJoin(TABLE_PROJECTPRODUCT)->alias('t3')->on('t1.project = t3.project')
|
||||
@@ -2593,6 +2601,8 @@ class storyModel extends model
|
||||
->beginIF($type == 'bybranch' and strpos($branchID, ',') !== false)->andWhere('t2.branch')->eq($branchParam)->fi()
|
||||
->beginIF(strpos('changed|closed', $type) !== false)->andWhere('t2.status')->eq($type)->fi()
|
||||
->beginIF($type == 'unclosed')->andWhere('t2.status')->in(array_keys($unclosedStatus))->fi()
|
||||
->beginIF($type == 'linkedexecution')->andWhere('t2.id')->in(array_keys($storyIdList))->fi()
|
||||
->beginIF($type == 'unlinkedexecution')->andWhere('t2.id')->notIn(array_keys($storyIdList))->fi()
|
||||
->fi()
|
||||
->beginIF($execution->type != 'project')
|
||||
->beginIF(!empty($productParam))->andWhere('t1.product')->eq($productParam)->fi()
|
||||
@@ -3302,19 +3312,6 @@ class storyModel extends model
|
||||
return $storyGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mail subject.
|
||||
*
|
||||
* @param object $story
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getSubject($story)
|
||||
{
|
||||
$productName = empty($story->product) ? '' : ' - ' . $this->loadModel('product')->getById($story->product)->name;
|
||||
return 'STORY #' . $story->id . ' ' . $story->title . $productName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get toList and ccList.
|
||||
*
|
||||
@@ -3880,63 +3877,6 @@ class storyModel extends model
|
||||
return $forceReview;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send mail
|
||||
*
|
||||
* @param int $storyID
|
||||
* @param int $actionID
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function sendmail($storyID, $actionID)
|
||||
{
|
||||
$this->loadModel('mail');
|
||||
$story = $this->getById($storyID);
|
||||
$users = $this->loadModel('user')->getPairs('noletter');
|
||||
|
||||
/* Get actions. */
|
||||
$action = $this->loadModel('action')->getById($actionID);
|
||||
$history = $this->action->getHistory($actionID);
|
||||
$action->history = isset($history[$actionID]) ? $history[$actionID] : array();
|
||||
$action->appendLink = '';
|
||||
if(strpos($action->extra, ':') !== false)
|
||||
{
|
||||
list($extra, $id) = explode(':', $action->extra);
|
||||
$action->extra = $extra;
|
||||
if($id)
|
||||
{
|
||||
$name = $this->dao->select('title')->from(TABLE_STORY)->where('id')->eq($id)->fetch('title');
|
||||
if($name) $action->appendLink = html::a(zget($this->config->mail, 'domain', common::getSysURL()) . helper::createLink($action->objectType, 'view', "id=$id", 'html'), "#$id " . $name);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get mail content. */
|
||||
$modulePath = $this->app->getModulePath($appName = '', 'story');
|
||||
$oldcwd = getcwd();
|
||||
$viewFile = $modulePath . 'view/sendmail.html.php';
|
||||
chdir($modulePath . 'view');
|
||||
if(file_exists($modulePath . 'ext/view/sendmail.html.php'))
|
||||
{
|
||||
$viewFile = $modulePath . 'ext/view/sendmail.html.php';
|
||||
chdir($modulePath . 'ext/view');
|
||||
}
|
||||
ob_start();
|
||||
include $viewFile;
|
||||
foreach(glob($modulePath . 'ext/view/sendmail.*.html.hook.php') as $hookFile) include $hookFile;
|
||||
$mailContent = ob_get_contents();
|
||||
ob_end_clean();
|
||||
chdir($oldcwd);
|
||||
|
||||
$sendUsers = $this->getToAndCcList($story, $action->action);
|
||||
if(!$sendUsers) return;
|
||||
list($toList, $ccList) = $sendUsers;
|
||||
$subject = $this->getSubject($story);
|
||||
|
||||
/* Send it. */
|
||||
$this->mail->send($toList, $subject, $mailContent, $ccList);
|
||||
if($this->mail->isError()) error_log(join("\n", $this->mail->getError()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tracks.
|
||||
*
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
* @link http://www.zentao.net
|
||||
*/
|
||||
?>
|
||||
<?php $mailTitle = 'STORY #' . $story->id . ' ' . $story->title;?>
|
||||
<?php $mailTitle = 'STORY #' . $object->id . ' ' . $object->title;?>
|
||||
<?php include $this->app->getModuleRoot() . 'common/view/mail.header.html.php';?>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellpadding='0' cellspacing='0' width='600' style='border: none; border-collapse: collapse;'>
|
||||
<tr>
|
||||
<td style='padding: 10px; background-color: #F8FAFE; border: none; font-size: 14px; font-weight: 500; border-bottom: 1px solid #e5e5e5;'>
|
||||
<?php $color = empty($story->color) ? '#333' : $story->color;?>
|
||||
<?php echo html::a(zget($this->config->mail, 'domain', common::getSysURL()) . helper::createLink('story', 'view', "storyID=$story->id", 'html'), $mailTitle, '', "style='color: {$color}; text-decoration: underline;'");?>
|
||||
<?php $color = empty($object->color) ? '#333' : $object->color;?>
|
||||
<?php echo html::a(zget($this->config->mail, 'domain', common::getSysURL()) . helper::createLink('story', 'view', "storyID=$object->id", 'html'), $mailTitle, '', "style='color: {$color}; text-decoration: underline;'");?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -28,7 +28,7 @@
|
||||
<td style='padding: 10px; border: none;'>
|
||||
<fieldset style='border: 1px solid #e5e5e5'>
|
||||
<legend style='color: #114f8e'><?php echo $this->lang->story->legendSpec;?></legend>
|
||||
<div style='padding:5px;'><?php echo $story->spec;?></div>
|
||||
<div style='padding:5px;'><?php echo $object->spec;?></div>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -3166,68 +3166,6 @@ class taskModel extends model
|
||||
echo !common::hasPriv('task', 'assignTo', $task) ? "<span style='padding-left: 21px' class='{$btnTextClass}'>{$assignedToText}</span>" : $assignToHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send mail.
|
||||
*
|
||||
* @param int $taskID
|
||||
* @param int $actionID
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function sendmail($taskID, $actionID)
|
||||
{
|
||||
$this->loadModel('mail');
|
||||
$task = $this->getById($taskID);
|
||||
$users = $this->loadModel('user')->getPairs('noletter');
|
||||
|
||||
/* Get action info. */
|
||||
$action = $this->loadModel('action')->getById($actionID);
|
||||
$history = $this->action->getHistory($actionID);
|
||||
$action->history = isset($history[$actionID]) ? $history[$actionID] : array();
|
||||
|
||||
/* Get mail content. */
|
||||
$oldcwd = getcwd();
|
||||
$modulePath = $this->app->getModulePath($appName = '', 'task');
|
||||
$viewFile = $modulePath . 'view/sendmail.html.php';
|
||||
chdir($modulePath . 'view');
|
||||
|
||||
if(file_exists($modulePath . 'ext/view/sendmail.html.php'))
|
||||
{
|
||||
$viewFile = $modulePath . 'ext/view/sendmail.html.php';
|
||||
chdir($modulePath . 'ext/view');
|
||||
}
|
||||
|
||||
ob_start();
|
||||
include $viewFile;
|
||||
foreach(glob($modulePath . 'ext/view/sendmail.*.html.hook.php') as $hookFile) include $hookFile;
|
||||
$mailContent = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
chdir($oldcwd);
|
||||
|
||||
$sendUsers = $this->getToAndCcList($task);
|
||||
if(!$sendUsers) return;
|
||||
list($toList, $ccList) = $sendUsers;
|
||||
$subject = $this->getSubject($task);
|
||||
|
||||
/* Send emails. */
|
||||
$this->mail->send($toList, $subject, $mailContent, $ccList);
|
||||
if($this->mail->isError()) error_log(join("\n", $this->mail->getError()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mail subject.
|
||||
*
|
||||
* @param object $task
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getSubject($task)
|
||||
{
|
||||
$executionName = $this->loadModel('execution')->getById($task->execution)->name;
|
||||
return 'TASK#' . $task->id . ' ' . $task->name . ' - ' . $executionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get toList and ccList.
|
||||
*
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
* @link http://www.zentao.net
|
||||
*/
|
||||
?>
|
||||
<?php $mailTitle = 'TASK #' . $task->id . ' ' . $task->name;?>
|
||||
<?php $mailTitle = 'TASK #' . $object->id . ' ' . $object->name;?>
|
||||
<?php include $this->app->getModuleRoot() . 'common/view/mail.header.html.php';?>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellpadding='0' cellspacing='0' width='600' style='border: none; border-collapse: collapse;'>
|
||||
<tr>
|
||||
<td style='padding: 10px; background-color: #F8FAFE; border: none; font-size: 14px; font-weight: 500; border-bottom: 1px solid #e5e5e5;'>
|
||||
<?php $color = empty($task->color) ? '#333' : $task->color;?>
|
||||
<?php echo html::a(zget($this->config->mail, 'domain', common::getSysURL()) . helper::createLink('task', 'view', "taskID=$task->id", 'html'), $mailTitle, '', "style='color: {$color}; text-decoration: underline;'");?>
|
||||
<?php $color = empty($object->color) ? '#333' : $object->color;?>
|
||||
<?php echo html::a(zget($this->config->mail, 'domain', common::getSysURL()) . helper::createLink('task', 'view', "taskID=$object->id", 'html'), $mailTitle, '', "style='color: {$color}; text-decoration: underline;'");?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -28,7 +28,7 @@
|
||||
<td style='padding: 10px; border: none;'>
|
||||
<fieldset style='border: 1px solid #e5e5e5'>
|
||||
<legend style='color: #114f8e'><?php echo $this->lang->task->legendDesc;?></legend>
|
||||
<div style='padding:5px;'><?php echo $task->desc;?></div>
|
||||
<div style='padding:5px;'><?php echo $object->desc;?></div>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1543,77 +1543,6 @@ class testtaskModel extends model
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send mail.
|
||||
*
|
||||
* @param int $testtaskID
|
||||
* @param int $actionID
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function sendmail($testtaskID, $actionID)
|
||||
{
|
||||
$this->loadModel('mail');
|
||||
$testtask = $this->getByID($testtaskID);
|
||||
$users = $this->loadModel('user')->getPairs('noletter');
|
||||
|
||||
/* Get action info. */
|
||||
$action = $this->loadModel('action')->getById($actionID);
|
||||
$history = $this->action->getHistory($actionID);
|
||||
$action->history = isset($history[$actionID]) ? $history[$actionID] : array();
|
||||
|
||||
/* Get mail content. */
|
||||
$modulePath = $this->app->getModulePath($appName = '', 'testtask');
|
||||
$oldcwd = getcwd();
|
||||
$viewFile = $modulePath . 'view/sendmail.html.php';
|
||||
chdir($modulePath . 'view');
|
||||
if(file_exists($modulePath . 'ext/view/sendmail.html.php'))
|
||||
{
|
||||
$viewFile = $modulePath . 'ext/view/sendmail.html.php';
|
||||
chdir($modulePath . 'ext/view');
|
||||
}
|
||||
ob_start();
|
||||
include $viewFile;
|
||||
foreach(glob($modulePath . 'ext/view/sendmail.*.html.hook.php') as $hookFile) include $hookFile;
|
||||
$mailContent = ob_get_contents();
|
||||
ob_end_clean();
|
||||
chdir($oldcwd);
|
||||
|
||||
$sendUsers = $this->getToAndCcList($testtask);
|
||||
if(!$sendUsers) return;
|
||||
list($toList, $ccList) = $sendUsers;
|
||||
$subject = $this->getSubject($testtask, $action->action);
|
||||
|
||||
/* Send mail. */
|
||||
$this->mail->send($toList, $subject, $mailContent, $ccList);
|
||||
if($this->mail->isError()) error_log(join("\n", $this->mail->getError()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mail subject.
|
||||
*
|
||||
* @param object $testtask
|
||||
* @param string $actionType
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getSubject($testtask, $actionType)
|
||||
{
|
||||
/* Set email title. */
|
||||
if($actionType == 'opened')
|
||||
{
|
||||
return sprintf($this->lang->testtask->mail->create->title, $this->app->user->realname, $testtask->id, $testtask->name);
|
||||
}
|
||||
elseif($actionType == 'closed')
|
||||
{
|
||||
return sprintf($this->lang->testtask->mail->close->title, $this->app->user->realname, $testtask->id, $testtask->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
return sprintf($this->lang->testtask->mail->edit->title, $this->app->user->realname, $testtask->id, $testtask->name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get toList and ccList.
|
||||
*
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
* @link http://www.zentao.net
|
||||
*/
|
||||
?>
|
||||
<?php $mailTitle = 'TESTTASK #' . $testtask->id . ' ' . $testtask->name;?>
|
||||
<?php $mailTitle = 'TESTTASK #' . $object->id . ' ' . $object->name;?>
|
||||
<?php include $this->app->getModuleRoot() . 'common/view/mail.header.html.php';?>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellpadding='0' cellspacing='0' width='600' style='border: none; border-collapse: collapse;'>
|
||||
<tr>
|
||||
<td style='padding: 10px; background-color: #F8FAFE; border: none; font-size: 14px; font-weight: 500; border-bottom: 1px solid #e5e5e5;'>
|
||||
<?php $color = empty($testtask->color) ? '#333' : $testtask->color;?>
|
||||
<?php echo html::a(zget($this->config->mail, 'domain', common::getSysURL()) . helper::createLink('testtask', 'view', "testtaskID=$testtask->id", 'html'), $mailTitle, '', "style='color: {$color}; text-decoration: underline;'");?>
|
||||
<?php $color = empty($object->color) ? '#333' : $object->color;?>
|
||||
<?php echo html::a(zget($this->config->mail, 'domain', common::getSysURL()) . helper::createLink('testtask', 'view', "testtaskID=$object->id", 'html'), $mailTitle, '', "style='color: {$color}; text-decoration: underline;'");?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -28,7 +28,7 @@
|
||||
<td style='padding: 10px; border: none;'>
|
||||
<fieldset style='border: 1px solid #e5e5e5'>
|
||||
<legend style='color: #114f8e'><?php echo $this->lang->testtask->desc;?></legend>
|
||||
<div style='padding:5px;'><?php echo $testtask->desc;?></div>
|
||||
<div style='padding:5px;'><?php echo $object->desc;?></div>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -50,7 +50,7 @@ $lang->upgrade->sureExecute = 'Execute';
|
||||
$lang->upgrade->forbiddenExt = 'The extension is incompatible with the version. It has been deactivated:';
|
||||
$lang->upgrade->updateFile = 'File information has to be updated.';
|
||||
$lang->upgrade->noticeSQL = 'Your database is inconsistent with the standard and it failed to fix it. Please run the following SQL and refresh.';
|
||||
$lang->upgrade->afterDeleted = 'File is not deleted. Please refresh after you delete it.';
|
||||
$lang->upgrade->afterDeleted = 'Please execute commands to delete the files. Please refresh after you delete them.';
|
||||
$lang->upgrade->mergeProgram = 'Data Merge';
|
||||
$lang->upgrade->mergeTips = 'Data Migration Tips';
|
||||
$lang->upgrade->toPMS15Guide = 'ZenTao open source version 15 upgrade';
|
||||
|
||||
@@ -50,7 +50,7 @@ $lang->upgrade->sureExecute = '确认执行';
|
||||
$lang->upgrade->forbiddenExt = '以下插件与新版本不兼容,已经自动禁用:';
|
||||
$lang->upgrade->updateFile = '需要更新附件信息。';
|
||||
$lang->upgrade->noticeSQL = '检查到你的数据库跟标准不一致,尝试修复失败。请执行以下SQL语句,再刷新页面检查。';
|
||||
$lang->upgrade->afterDeleted = '以上文件未能删除, 删除后刷新!';
|
||||
$lang->upgrade->afterDeleted = '请执行上面命令删除文件, 删除后刷新!';
|
||||
$lang->upgrade->mergeProgram = '数据迁移';
|
||||
$lang->upgrade->mergeTips = '数据迁移提示';
|
||||
$lang->upgrade->toPMS15Guide = '禅道开源版15版本升级';
|
||||
|
||||
@@ -1050,10 +1050,11 @@ class upgradeModel extends model
|
||||
$fullPath = $basePath . str_replace('/', DIRECTORY_SEPARATOR, $file);
|
||||
if(file_exists($fullPath))
|
||||
{
|
||||
if((is_dir($fullPath) and !$zfile->removeDir($fullPath)) or
|
||||
(is_file($fullPath) and !$zfile->removeFile($fullPath)))
|
||||
$isDir = is_dir($fullPath);
|
||||
if(($isDir and !$zfile->removeDir($fullPath)) or
|
||||
(!$isDir and !$zfile->removeFile($fullPath)))
|
||||
{
|
||||
$result[] = $fullPath;
|
||||
$result[] = 'rm -f ' . ($isDir ? '-r ' : '') . $fullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4441,6 +4442,7 @@ class upgradeModel extends model
|
||||
|
||||
$this->dao->insert(TABLE_PROJECT)->data($project)
|
||||
->batchcheck('name', 'notempty')
|
||||
->check('name', 'unique', "type='project'")
|
||||
->exec();
|
||||
if(dao::isError()) return false;
|
||||
|
||||
|
||||
@@ -942,14 +942,6 @@ class user extends control
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!empty($this->config->global->showDemoUsers))
|
||||
{
|
||||
$demoUsers = 'productManager,projectManager,dev1,dev2,dev3,tester1,tester2,tester3,testManager';
|
||||
if($this->app->getClientLang() == 'en') $demoUsers = 'thePO,pm1,pm2,pg1,pg2,pg3,thePM,qa1,theQS';
|
||||
$demoUsers = $this->dao->select('account,password,realname')->from(TABLE_USER)->where('account')->in($demoUsers)->andWhere('deleted')->eq(0)->fetchAll('account');
|
||||
$this->view->demoUsers = $demoUsers;
|
||||
}
|
||||
|
||||
$this->loadModel('misc');
|
||||
$this->view->noGDLib = sprintf($this->lang->misc->noGDLib, common::getSysURL() . $this->config->webRoot, '', false, true);
|
||||
$this->view->title = $this->lang->user->login;
|
||||
|
||||
@@ -1234,8 +1234,7 @@ class userModel extends model
|
||||
if(!$uploadResult) return array('result' => 'fail', 'message' => $this->lang->fail);
|
||||
|
||||
$fileIdList = array_keys($uploadResult);
|
||||
$file = $this->file->getByID($fileIdList[0]);
|
||||
$this->dao->update(TABLE_USER)->set('avatar')->eq($file->webPath)->where('account')->eq($this->app->user->account)->exec();
|
||||
$file = $this->file->getByID($fileIdList[0]);
|
||||
|
||||
return array('result' => 'success', 'message' => '', 'locate' => helper::createLink('user', 'cropavatar', "image={$file->id}"));
|
||||
}
|
||||
@@ -1615,8 +1614,9 @@ class userModel extends model
|
||||
}
|
||||
|
||||
/* Compute parent stakeholders. */
|
||||
$programStakeholderGroup = $this->loadModel('stakeholder')->getParentStakeholderGroup(array_keys($allPrograms));
|
||||
$projectStakeholderGroup = $this->loadModel('stakeholder')->getParentStakeholderGroup(array_keys($allProjects));
|
||||
$this->loadModel('stakeholder');
|
||||
$programStakeholderGroup = $this->stakeholder->getParentStakeholderGroup(array_keys($allPrograms));
|
||||
$projectStakeholderGroup = $this->stakeholder->getParentStakeholderGroup(array_keys($allProjects));
|
||||
|
||||
list($productTeams, $productStakeholders) = $this->getProductMembers($allProducts);
|
||||
|
||||
|
||||
@@ -72,17 +72,23 @@ if(empty($config->notMd5Pwd))js::import($jsRoot . 'md5.js');
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php if(isset($demoUsers)):?>
|
||||
<?php if(!empty($this->config->global->showDemoUsers)):?>
|
||||
<?php
|
||||
$demoPassword = '123456';
|
||||
$md5Password = md5('123456');
|
||||
$demoUsers = 'productManager,projectManager,dev1,dev2,dev3,tester1,tester2,tester3,testManager';
|
||||
if($this->app->getClientLang() == 'en') $demoUsers = 'thePO,pm1,pm2,pg1,pg2,pg3,thePM,qa1,theQS';
|
||||
$demoUsers = $this->dao->select('account,password,realname')->from(TABLE_USER)->where('account')->in($demoUsers)->andWhere('deleted')->eq(0)->andWhere('password')->eq($md5Password)->fetchAll('account');
|
||||
?>
|
||||
<footer>
|
||||
<span><?php echo $lang->user->loginWithDemoUser;?></span>
|
||||
<?php
|
||||
$password = md5('123456');
|
||||
$link = inlink('login');
|
||||
$link .= strpos($link, '?') !== false ? '&' : '?';
|
||||
$link = inlink('login');
|
||||
$link .= strpos($link, '?') !== false ? '&' : '?';
|
||||
foreach($demoUsers as $demoAccount => $demoUser)
|
||||
{
|
||||
if($demoUser->password != $password) continue;
|
||||
echo html::a($link . "account={$demoAccount}&password=" . md5($password . $this->session->rand), $demoUser->realname);
|
||||
if($demoUser->password != $md5Password) continue;
|
||||
echo html::a($link . "account={$demoAccount}&password=" . md5($md5Password . $this->session->rand), $demoUser->realname);
|
||||
}
|
||||
?>
|
||||
</footer>
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 200 KiB |
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user