This commit is contained in:
hufangzhou
2021-08-25 16:14:42 +08:00
64 changed files with 1011 additions and 572 deletions
+51
View File
@@ -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');
}
}
+50
View File
@@ -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
View File
@@ -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';
+4
View File
@@ -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;
+2 -1
View File
@@ -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)));
+109
View File
@@ -211,6 +211,115 @@ class html extends baseHTML
$value = str_replace("'", '&#039;', $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();
-71
View File
@@ -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.
*
+4 -4
View File
@@ -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>
+2 -6
View File
@@ -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
View File
@@ -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();
+2 -2
View File
@@ -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()
{
+2 -2
View File
@@ -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
View File
@@ -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.
*
+4 -4
View File
@@ -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>
+1
View File
@@ -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;}
+1 -1
View File
@@ -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()
+4 -4
View File
@@ -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') ? '&nbsp;&nbsp;&nbsp;' : '';
$teamPairs[$id] = $prefix . $team->team;
$teamPairs[$id] = $prefix . $team->name;
}
return $teamPairs;
+11 -3
View File
@@ -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;
+1
View File
@@ -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.";
+130
View File
@@ -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;
}
}
+1 -2
View File
@@ -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);
}
}
+1 -1
View File
@@ -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);
+6 -3
View File
@@ -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);}
+2
View File
@@ -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()
+1 -1
View File
@@ -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>
+4 -4
View File
@@ -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>
+2
View File
@@ -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();
+1 -1
View File
@@ -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)
{
+2 -1
View File
@@ -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&param=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&param=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' : '') . "'");
}
}
?>
+6
View File
@@ -67,6 +67,12 @@ class program extends control
$this->display();
}
public function kanban()
{
$this->view->kanbanGroup = $this->program->getKanbanGroup();
$this->display();
}
/**
* Program products list.
*
+14
View File
@@ -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;}
+3
View File
@@ -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.';
+3
View File
@@ -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.';
+3
View File
@@ -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.';
+3
View File
@@ -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.';
+13
View File
@@ -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
View File
@@ -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
+3 -5
View File
@@ -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;?>
+129
View File
@@ -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';?>
+2 -2
View File
@@ -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();
+41 -41
View File
@@ -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'>
+6 -4
View File
@@ -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;
+6 -4
View File
@@ -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;
+1 -48
View File
@@ -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, ',');
+3 -3
View File
@@ -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
View File
@@ -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.
*
+4 -4
View File
@@ -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>
-62
View File
@@ -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.
*
+4 -4
View File
@@ -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>
-71
View File
@@ -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.
*
+4 -4
View File
@@ -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>
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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版本升级';
+5 -3
View File
@@ -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;
-8
View File
@@ -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;
+4 -4
View File
@@ -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);
+12 -6
View File
@@ -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.