diff --git a/api/v1/entries/issue.php b/api/v1/entries/issue.php new file mode 100644 index 0000000000..8716099236 --- /dev/null +++ b/api/v1/entries/issue.php @@ -0,0 +1,51 @@ +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'); + } +} diff --git a/api/v1/entries/issues.php b/api/v1/entries/issues.php new file mode 100644 index 0000000000..3cfb8d9df4 --- /dev/null +++ b/api/v1/entries/issues.php @@ -0,0 +1,50 @@ +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')); + } +} diff --git a/config/filter.php b/config/filter.php index f5da683e22..4aec7e207e 100644 --- a/config/filter.php +++ b/config/filter.php @@ -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'; diff --git a/config/routes.php b/config/routes.php index ce9d1326b1..32f5743b6e 100644 --- a/config/routes.php +++ b/config/routes.php @@ -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; diff --git a/framework/base/control.class.php b/framework/base/control.class.php index ac94b47fc7..4a20b7cd9e 100644 --- a/framework/base/control.class.php +++ b/framework/base/control.class.php @@ -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))); diff --git a/lib/front/front.class.php b/lib/front/front.class.php index 102c8cb7bf..671e077dd2 100644 --- a/lib/front/front.class.php +++ b/lib/front/front.class.php @@ -211,6 +211,115 @@ class html extends baseHTML $value = str_replace("'", ''', $value); return "\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 .= '' . strtoupper($user->account[0]) . ''; + + $html .= ""; + + 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); + } } /** diff --git a/module/block/view/recentprojectblock.html.php b/module/block/view/recentprojectblock.html.php index b0fa93205c..f4bdb3bdf8 100644 --- a/module/block/view/recentprojectblock.html.php +++ b/module/block/view/recentprojectblock.html.php @@ -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 @@ project->hoursUnit, $project->estimate); ?> systemMode == 'new'):?> - model === 'waterfall'): ?> + model !== 'waterfall'): ?>
user->account == 'guest'; echo ""; - echo "
"; - echo !empty($app->user->avatar) ? html::image($app->user->avatar) : strtoupper($app->user->account[0]); - echo "
\n"; + echo html::avatar($app->user); echo '
'; echo "
-
+ diff --git a/module/my/view/profile.html.php b/module/my/view/profile.html.php index e4c3e8a5c7..8ffeb53c48 100644 --- a/module/my/view/profile.html.php +++ b/module/my/view/profile.html.php @@ -16,13 +16,13 @@
- +
+ id='avatarForm' enctype='multipart/form-data'> - avatar ? html::image($user->avatar) : strtoupper($user->account[0]);?> - + ', '', "class='btn-avatar' id='avatarUploadBtn' data-toggle='tooltip' data-container='body' data-placement='bottom' title='{$lang->my->uploadAvatar}'");?> - +
realname;?> user->roleList, $user->role, '');?>
diff --git a/module/pipeline/model.php b/module/pipeline/model.php index 5e7fdba12e..56b7d2df6d 100644 --- a/module/pipeline/model.php +++ b/module/pipeline/model.php @@ -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(); diff --git a/module/program/view/browsebylist.html.php b/module/program/view/browsebylist.html.php index 4e59f68dc1..543e936c88 100644 --- a/module/program/view/browsebylist.html.php +++ b/module/program/view/browsebylist.html.php @@ -13,7 +13,7 @@
- + @@ -62,9 +62,7 @@
project->budget);?> project->begin);?> project->end);?>project->progress;?>project->progress;?> actions;?>
project->statusList, $program->status, '');?> PM)):?> -
- PM]) ? html::image($usersAvatar[$program->PM]) : strtoupper($program->PM[0]);?> -
+ $usersAvatar[$program->PM], 'account' => $program->PM)); ?> PM]) ? $PMList[$program->PM]->id : '';?> PM);?> createLink('user', 'profile', "userID=$userID", '', true), $userName, '', "title='{$userName}' data-toggle='modal' data-type='iframe' data-width='600'");?> @@ -76,7 +74,7 @@
end == LONG_TIME ? $lang->program->longTime : $program->end;?> id])):?> -
+
id];?>
diff --git a/module/project/view/browsebycard.html.php b/module/project/view/browsebycard.html.php index fd7f10253f..4c4e3366ec 100644 --- a/module/project/view/browsebycard.html.php +++ b/module/project/view/browsebycard.html.php @@ -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;} @@ -138,41 +140,39 @@
-