This commit is contained in:
hufangzhou
2021-08-25 16:14:42 +08:00
64 changed files with 1011 additions and 572 deletions
@@ -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>