3 * FusionForge Generic Tracker facility
5 * Copyright 1999-2001 (c) VA Linux Systems - Sourceforge
6 * Copyright 2010 (c) Fusionforge Team
7 * Copyright 2011, Franck Villaume - Capgemini
8 * Copyright (C) 2011 Alain Peyrat - Alcatel-Lucent
9 * Copyright 2014,2016-2017, Franck Villaume - TrivialDev
10 * Copyright 2016-2017, Stéphane-Eymeric Bredthauer - TrivialDev
11 * http://fusionforge.org
13 * This file is part of FusionForge. FusionForge is free software;
14 * you can redistribute it and/or modify it under the terms of the
15 * GNU General Public License as published by the Free Software
16 * Foundation; either version 2 of the Licence, or (at your option)
19 * FusionForge is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License along
25 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
29 require_once $gfcommon.'tracker/ArtifactType.class.php';
30 require_once $gfcommon.'tracker/ArtifactExtraField.class.php';
31 require_once $gfcommon.'tracker/ArtifactExtraFieldElement.class.php';
32 require_once $gfcommon.'tracker/ArtifactWorkflow.class.php';
33 require_once $gfcommon.'tracker/EffortUnitFactory.class.php';
34 require_once $gfcommon.'include/utils_crossref.php';
35 require_once $gfcommon.'include/UserManager.class.php';
36 require_once $gfcommon.'widget/WidgetLayoutManager.class.php';
38 class ArtifactTypeHtml extends ArtifactType {
40 function header($params) {
42 if (!forge_get_config('use_tracker')) {
46 $group_id = $this->Group->getID();
48 //required by new site_project_header
49 $params['group'] = $group_id;
50 $params['toptab'] = 'tracker';
51 $params['tabtext'] = $this->getName();
57 $labels[] = _("View Trackers");
58 $links[] = '/tracker/?group_id='.$group_id;
59 $attr[] = array('title' => _('Get the list of available trackers'));
60 $labels[] = $this->getName();
61 $links[] = '/tracker/?func=browse&group_id='.$group_id.'&atid='. $this->getID();
62 $attr[] = array('title' => _('Browse this tracker.'));
63 $labels[] = _('Export CSV');
64 $links[] = '/tracker/?func=csv&group_id='.$group_id.'&atid='. $this->getID();
65 $attr[] = array('title' => _('Download data from this tracker as csv file.'));
66 if (forge_check_perm('tracker', $this->getID(),'submit')) {
67 $labels[] = _('Submit New');
68 $links[] = '/tracker/?func=add&group_id='.$group_id.'&atid='. $this->getID();
69 $attr[] = array('title' => _('Add a new issue.'));
72 if (session_loggedin()) {
73 $labels[] = _('Reporting');
74 $links[] = '/tracker/reporting/?group_id='.$group_id.'&atid='. $this->getID();
75 $attr[] = array('title' => _('Various graph about statistics.'));
76 if ($this->isMonitoring()) {
77 $labels[] = _('Stop Monitor');
78 $links[] = '/tracker/?group_id='.$group_id.'&atid='. $this->getID().'&func=monitor&stopmonitor=1';
79 $attr[] = array('title' => _('Remove this tracker from your monitoring.'));
81 $labels[] = _('Monitor');
82 $links[] = '/tracker/?group_id='.$group_id.'&atid='. $this->getID().'&func=monitor&startmonitor=1';
83 $attr[] = array('title' => _('Add this tracker from your monitoring.'));
86 if (forge_check_perm('tracker_admin', $group_id)) {
87 $labels[] = _('Administration');
88 $links[] = '/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID();
89 $attr[] = array('title' => _('Global administration for trackers. Create, clone, workflow, fields ...'));
91 if (forge_get_config('use_tracker_widget_display')) {
92 $sql = "SELECT l.* FROM layouts AS l INNER JOIN owner_layouts AS o ON(l.id = o.layout_id)
93 WHERE o.owner_type = $1
95 AND o.is_default = 1";
96 $res = db_query_params($sql,array('t', $this->getID()));
97 if($res && db_numrows($res) < 1) {
98 $lm = new WidgetLayoutManager();
99 $lm->createDefaultLayoutForTracker($this->getID());
100 $res = db_query_params($sql,array('t', $this->getID()));
102 $id = db_result($res, 0 , 'id');
103 $url = '/widgets/widgets.php?owner=t'.$this->getID().'&layout_id='.$id;
104 $labels[] = _('Add widgets');
105 $labels[] = _('Customize Layout');
106 $attr[] = array('title' => _('Customfields must be linked to a widget to be displayed. Use “Add widgets” to create new widget to link and organize your customfields.'));
107 $attr[] = array('title' => _('General layout to display “Submit New” form or detailed view of an existing artifact can be customize. Use “Customize Layout” to that purpose.'));
109 $links[] = $url.'&update=layout';
114 $params['submenu'] = $HTML->subMenu($labels, $links, $attr);
115 site_project_header($params);
118 plugin_hook('blocks', 'tracker_'.$this->getName());
122 function footer($params = array()) {
123 site_project_footer($params);
126 function adminHeader($params) {
128 $this->header($params);
129 $group_id= $this->Group->getID();
130 $elementLi[] = array('content' => util_make_link('/tracker/admin/?group_id='.$group_id, _('New Tracker'), array('title'=>_('Create a new tracker.'))));
131 $elementLi[] = array('content' => util_make_link('/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&update_type=1', _('Update Settings'), array('title'=>_('Set up preferences like expiration times, email addresses.'))));
132 $elementLi[] = array('content' => util_make_link('/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&effort_units=1', _('Manage Effort Units'), array('title'=>_('Manage Effort Units for Effort custom extra field.'))));
133 $elementLi[] = array('content' => util_make_link('/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&add_extrafield=1', _('Manage Custom Fields'), array('title'=>_('Add new boxes like Phases, Quality Metrics, Components, etc. Once added they can be used with other selection boxes (for example, Categories or Groups) to describe and browse bugs or other artifact types.'))));
134 $elementLi[] = array('content' => util_make_link('/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&workflow=1', _('Manage Workflow'), array('title'=>_('Edit tracker workflow.'))));
135 $elementLi[] = array('content' => util_make_link('/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&customize_list=1', _('Customize List'), array('title'=>_('Customize display for the tracker.'))));
136 $elementLi[] = array('content' => util_make_link('/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&add_canned=1', _('Manage Canned Responses'), array('title'=>_('Create/change generic response messages for the tracker.'))));
137 $elementLi[] = array('content' => util_make_link('/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&clone_tracker=1', _('Apply Template Tracker'), array('title'=>_('Duplicate parameters and fields from a template trackers in this one.'))));
138 $elementLi[] = array('content' => util_make_link('/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&delete=1', _('Delete'), array('title'=>_('Permanently delete this tracker.'))));
139 echo $HTML->html_list($elementLi);
142 function adminFooter($params) {
143 $this->footer($params);
146 function renderSubmitInstructions() {
147 $msg = $this->getSubmitInstructions();
148 return str_replace("\n","<br />", $msg);
151 function renderBrowseInstructions() {
152 $msg = $this->getBrowseInstructions();
153 return str_replace("\n","<br />", $msg);
157 * renderExtraFields - ???
159 * @param array $selected
160 * @param bool $show_100 Display the specific '100' value. Default is false.
161 * @param string $text_100 Label displayed for the '100' value. Default is 'none'
162 * @param bool $show_any
163 * @param string $text_any
164 * @param array $types
165 * @param bool $status_show_100 Force display of the '100' value if needed. Default is false.
166 * @param string $mode QUERY, DISPLAY, UPDATE, NEW
168 function renderExtraFields($selected = array(),
169 $show_100 = false, $text_100 = 'none',
170 $show_any = false, $text_any = 'Any',
172 $status_show_100 = false,
177 if ($mode == 'NEW') {
178 $efarr = $this->getExtraFields($types, false, false);
180 $efarr = $this->getExtraFields($types);
182 //each two columns, we'll reset this and start a new row
183 $template = $this->getRenderHTML($types, $mode);
185 if ($mode=='QUERY') {
186 $keys=array_keys($efarr);
187 for ($k=0; $k<count($keys); $k++) {
189 $type = $efarr[$i]['field_type'];
190 if ($type == ARTIFACT_EXTRAFIELDTYPE_SELECT ||
191 $type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX ||
192 $type == ARTIFACT_EXTRAFIELDTYPE_RADIO ||
193 $type == ARTIFACT_EXTRAFIELDTYPE_STATUS ||
194 $type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT) {
195 $efarr[$i]['field_type'] = ARTIFACT_EXTRAFIELDTYPE_MULTISELECT;
196 } elseif ($type == ARTIFACT_EXTRAFIELDTYPE_USER ||
197 $type == ARTIFACT_EXTRAFIELDTYPE_MULTIUSER) {
198 $efarr[$i]['field_type'] = ARTIFACT_EXTRAFIELDTYPE_MULTIUSER;
199 } elseif ($type == ARTIFACT_EXTRAFIELDTYPE_RELEASE ||
200 $type == ARTIFACT_EXTRAFIELDTYPE_MULTIRELEASE) {
201 $efarr[$i]['field_type'] = ARTIFACT_EXTRAFIELDTYPE_MULTIRELEASE;
202 } elseif ($type == ARTIFACT_EXTRAFIELDTYPE_DATETIME ||
203 $type == ARTIFACT_EXTRAFIELDTYPE_DATE) {
204 $efarr[$i]['field_type'] = ARTIFACT_EXTRAFIELDTYPE_DATERANGE;
205 } elseif ($type == ARTIFACT_EXTRAFIELDTYPE_EFFORT) {
206 $efarr[$i]['field_type'] = ARTIFACT_EXTRAFIELDTYPE_EFFORTRANGE;
208 $efarr[$i]['field_type'] = ARTIFACT_EXTRAFIELDTYPE_TEXT;
213 // 'DISPLAY' mode is for rendering in 'read-only' mode (for detail view).
214 if ($mode === 'DISPLAY') {
215 $keys=array_keys($efarr);
216 for ($k=0; $k<count($keys); $k++) {
220 if (!isset($selected[$efarr[$i]['extra_field_id']]))
221 $selected[$efarr[$i]['extra_field_id']] = '';
223 $value = $selected[$efarr[$i]['extra_field_id']];
224 $type = $efarr[$i]['field_type'];
226 if ($type == ARTIFACT_EXTRAFIELDTYPE_SELECT ||
227 $type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX ||
228 $type == ARTIFACT_EXTRAFIELDTYPE_RADIO ||
229 $type == ARTIFACT_EXTRAFIELDTYPE_STATUS ||
230 $type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT) {
232 $value = $efarr[$i]['show100label'];
234 $arr = $this->getExtraFieldElements($efarr[$i]['extra_field_id']);
236 // Convert the values (ids) to names in the ids order.
238 for ($j=0; $j<count($arr); $j++) {
239 if (is_array($value) && in_array($arr[$j]['element_id'], $value)) {
240 $new[]= $arr[$j]['element_name'];
241 } elseif ($arr[$j]['element_id'] === $value) {
242 $new[] = $arr[$j]['element_name'];
245 $value = join('<br />', $new);
247 } elseif ($type == ARTIFACT_EXTRAFIELDTYPE_TEXT ||
248 $type == ARTIFACT_EXTRAFIELDTYPE_TEXTAREA) {
249 $value = preg_replace('/((http|https|ftp):\/\/\S+)/',
250 "<a href=\"\\1\" target=\"_blank\">\\1</a>", $value);
251 } elseif ($type == ARTIFACT_EXTRAFIELDTYPE_RELATION || $type == ARTIFACT_EXTRAFIELDTYPE_PARENT) {
252 // Convert artifact id to links.
253 $value = preg_replace_callback('/\b(\d+)\b/', create_function('$matches', 'return _artifactid2url($matches[1], \'title\');'), $value);
254 } elseif ($type == ARTIFACT_EXTRAFIELDTYPE_DATETIME && $value!='') {
255 $value = date('Y-m-d H:i', $value);
256 } elseif ($type == ARTIFACT_EXTRAFIELDTYPE_EFFORT) {
257 if (!isset($effortUnitSet)) {
258 $effortUnitSet = New EffortUnitSet($this, $this->getEffortUnitSet());
259 $effortUnitFactory = New EffortUnitFactory($effortUnitSet);
261 $value = $effortUnitFactory->encodedToString($value);
263 $template = str_replace('{$PostName:'.$efarr[$i]['field_name'].'}', $post_name, $template);
264 $template = str_replace('{$'.$efarr[$i]['field_name'].'}', $value, $template);
269 if ($mode == 'UPDATE' || $mode == 'NEW') {
270 if ($mode == 'NEW') {
271 $efInFormula = $this->getExtraFieldsInFormula($types, false, false);
272 $efWithFormula = $this->getExtraFieldsWithFormula($types, false, false);
274 $efInFormula = $this->getExtraFieldsInFormula($types);
275 $efWithFormula = $this->getExtraFieldsWithFormula($types);
279 $keys = array_keys($efarr);
280 for ($k = 0; $k < count($keys); $k++) {
285 if (!empty($efarr[$i]['description'])) {
286 $attrs['title'] = $efarr[$i]['description'];
288 if ($efarr[$i]['is_required'] == 1 && $mode != 'QUERY') {
289 $attrs['required'] = 'required';
292 if ($mode == 'UPDATE' || $mode == 'NEW') {
293 if (in_array($efarr[$i]['extra_field_id'], $efInFormula)) {
294 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'in-formula';
296 if (in_array($efarr[$i]['extra_field_id'], $efWithFormula)) {
297 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'with-formula readonly';
298 $attrs['readonly'] = 'readonly';
302 if (!isset($selected[$efarr[$i]['extra_field_id']]))
303 $selected[$efarr[$i]['extra_field_id']] = '';
305 if ($status_show_100) {
306 $efarr[$i]['show100'] = $status_show_100;
311 if ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_SELECT ||
312 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX ||
313 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_RADIO ||
314 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT) {
315 if (!is_null($efarr[$i]['parent']) && !empty($efarr[$i]['parent']) && $efarr[$i]['parent']!='100') {
316 $aefParentId = $efarr[$i]['parent'];
317 $selectedElmnts = (isset($selected[$aefParentId]) ? $selected[$aefParentId] : '');
318 $aef = new ArtifactExtraField($this, $efarr[$i]['extra_field_id']);
319 $allowed = $aef->getAllowedValues($selectedElmnts);
323 if ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_SELECT) {
324 $str = $this->renderSelect($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['show100'], $efarr[$i]['show100label'], $show_any, $text_any, $allowed, $attrs);
326 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) {
328 $str = $this->renderCheckbox($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['show100'], $efarr[$i]['show100label'], $allowed, $attrs);
330 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_RADIO) {
332 $str = $this->renderRadio($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['show100'], $efarr[$i]['show100label'], $show_any, $text_any, $allowed, $attrs);
334 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_TEXT ||
335 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_INTEGER) {
337 if (!empty($efarr[$i]['pattern'])) {
338 $attrs['pattern'] = $efarr[$i]['pattern'];
340 if ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_TEXT) {
341 $str = $this->renderTextField($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['attribute1'], $efarr[$i]['attribute2'], $attrs);
343 $str = $this->renderIntegerField($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['attribute1'], $efarr[$i]['attribute2'], $attrs);
345 if ($mode == 'QUERY') {
346 $post_name = ' <i>'._('(%% for wildcards)').'</i> ';
349 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_TEXTAREA) {
351 $str = $this->renderTextArea($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['attribute1'], $efarr[$i]['attribute2'], $attrs);
352 if ($mode == 'QUERY') {
353 $post_name = ' <i>'._('(%% for wildcards)').'</i>';
356 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT) {
358 $str = $this->renderMultiSelectBox($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['show100'], $efarr[$i]['show100label'], $allowed, $attrs);
360 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
361 // parent artifact can't be close if a child is still open
362 if ($mode == 'UPDATE' &&
363 $efarr[$i]['aggregation_rule'] == ARTIFACT_EXTRAFIELD_AGGREGATION_RULE_STATUS_CLOSE_RESTRICTED &&
364 $ah->hasChildren()) {
365 $children = $ah->getChildren();
367 foreach ($children as $child) {
368 if ($child['status_id'] == 1) {
374 $aef = new ArtifactExtraField($this, $efarr[$i]['extra_field_id']);
375 $statusArr = $aef->getAvailableValues();
376 $openStatus = array();
377 foreach ($statusArr as $status) {
378 if ($status['status_id']==1) {
379 $openStatus[] = $status['element_id'];
383 $allowed = array_intersect($allowed, $openStatus);
385 $allowed = $openStatus;
390 // Get the allowed values from the workflow.
391 $atw = new ArtifactWorkflow($this, $efarr[$i]['extra_field_id']);
393 // Special treatment for the initial step (Submit).
394 // In this case, the initial value is the first value.
395 if (isset($selected[$efarr[$i]['extra_field_id']]) && $selected[$efarr[$i]['extra_field_id']]) {
396 $selected_node = $selected[$efarr[$i]['extra_field_id']];
398 $selected_node = 100;
402 $allowedWF = $atw->getNextNodes($selected_node);
404 $allowed = array_intersect($allowed, $allowedWF);
406 $allowed = $allowedWF;
408 $allowed[] = $selected_node;
409 $str = $this->renderSelect($efarr[$i]['extra_field_id'], $selected_node, $status_show_100, $text_100, $show_any, $text_any, $allowed, $attrs);
411 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_RELATION) {
412 $str = $this->renderRelationField($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['attribute1'], $efarr[$i]['attribute2'], $attrs);
413 if ($mode == 'UPDATE' || $mode == 'NEW') {
414 $post_name = $HTML->getEditFieldPic(_('Click to edit'), $alt = _('Click to edit'), array('onclick'=>"switch2edit(this, 'show$i', 'edit$i')"));
416 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_PARENT) {
417 $str = $this->renderParentField($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['attribute1'], $efarr[$i]['attribute2'], $attrs);
418 if ($mode == 'UPDATE' || $mode == 'NEW') {
419 $post_name = $HTML->getEditFieldPic(_('Click to edit'), $alt = _('Click to edit'), array('onclick'=>"switch2edit(this, 'show$i', 'edit$i')"));
421 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_DATETIME) {
422 $str = $this->renderDatetime($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $attrs);
423 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_USER) {
424 $str = $this->renderUserField($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['show100'], $efarr[$i]['show100label'], $show_any, $text_any,false, $attrs);
425 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_MULTIUSER) {
426 $str = $this->renderMultiUserField($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['show100'], $efarr[$i]['show100label'], $show_any, $text_any,false, $attrs);
427 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_RELEASE) {
428 $str = $this->renderReleaseField($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['show100'], $efarr[$i]['show100label'], $show_any, $text_any,false, $attrs);
429 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_MULTIRELEASE) {
430 $str = $this->renderMultiReleaseField($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['show100'], $efarr[$i]['show100label'], $show_any, $text_any,false, $attrs);
431 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_DATERANGE) {
432 if ($mode == 'QUERY') {
433 $post_name = ' <i>'._('(YYYY-MM-DD YYYY-MM-DD Format)').'</i>';
435 $str = $this->renderDateRange($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $attrs);
436 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_EFFORT) {
437 $str = $this->renderEffort($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['attribute1'], $efarr[$i]['attribute2'], $attrs);
438 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_EFFORTRANGE) {
439 $str = $this->renderEffortRange($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['attribute1'], $efarr[$i]['attribute2'], $attrs);
440 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_SLA) {
441 $str = $this->renderSLAField($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $attrs);
443 $template = str_replace('{$PostName:'.$efarr[$i]['field_name'].'}', $post_name, $template);
444 $template = str_replace('{$'.$efarr[$i]['field_name'].'}', $str, $template);
446 if($template != NULL){
447 if ($mode == 'UPDATE' || $mode == 'NEW' || $mode == 'QUERY') {
448 echo $this->javascript();
454 function renderRelatedTasks($group, $ah, $formid = null) {
456 if (!$group->usesPM()) {
462 $tasks = $ah->getRelatedTasks();
463 $taskcount = db_numrows($tasks);
465 if (forge_check_perm('tracker_admin', $ah->ArtifactType->Group->getID())) {
470 $totalPercentage = 0;
471 if ($taskcount > 0) {
472 $title_arr = array();
473 $title_arr[] = _('Task Id and Summary');
474 $title_arr[] = _('Progress');
475 $title_arr[] = _('Start Date');
476 $title_arr[] = _('End Date');
477 $title_arr[] = _('Status');
478 (($is_admin) ? $title_arr[]=_('Remove Relation') : '');
479 $return .= $HTML->listTableTop($title_arr);
481 while ($taskinfo = db_fetch_array($tasks)) {
482 $totalPercentage += $taskinfo['percent_complete'];
483 $taskid = $taskinfo['project_task_id'];
484 $projectid = $taskinfo['group_project_id'];
485 $groupid = $taskinfo['group_id'];
486 if (forge_check_perm('pm', $projectid, 'read')) {
487 $summary = util_unconvert_htmlspecialchars($taskinfo['summary']);
488 $startdate = date(_('Y-m-d H:i'), $taskinfo['start_date']);
489 $enddate = date(_('Y-m-d H:i'), $taskinfo['end_date']);
490 $status = $taskinfo['status_name'];
492 <td>'.util_make_link('/pm/t_follow.php/'.$taskid, '[T'.$taskid.'] '.$summary).'</td>
493 <td><div class="percentbar" style="width: 100px;">
494 <div style="width:'.round($taskinfo['percent_complete']).'px;"></div></div></td>
495 <td>'.$startdate.'</td>
496 <td>'.$enddate.'</td>
497 <td>'.$status.' ('.$taskinfo['percent_complete'].'%)</td>'.
498 (($is_admin) ? '<td><input type="checkbox" '.(($formid) ? 'form="'.$formid.'"' : '').' name="remlink[]" value="'.$taskid.'" /></td>' : '').
502 $return .= $HTML->listTableBottom();
504 $return .= "\n<hr /><p style=\"text-align:right;\">";
505 $return .= _('Average completion rate')._(': ').(int)($totalPercentage/$taskcount).'%';
508 $return .= $HTML->information(_('No related tasks'));
513 function renderFiles($group_id, $ah) {
515 $file_list =& $ah->getFiles();
516 $count=count($file_list);
520 $return .= '<strong>'._("Attachments")._(':').'</strong>'.'<br/>';
522 $title_arr[] = _('Size');
523 $title_arr[] = _('Name');
524 $title_arr[] = _('Date');
525 $title_arr[] = _('By');
526 $title_arr[] = _('Download');
527 if (forge_check_perm('tracker', $this->getID(), 'tech')) {
530 $return .= $HTML->listTableTop($title_arr);
532 foreach ($file_list as $file) {
534 $return .= '<td>'.human_readable_bytes($file->getSize()).'</td>';
535 $return .= '<td>'.htmlspecialchars($file->getName()).'</td>';
536 $return .= '<td>'.date(_('Y-m-d H:i'), $file->getDate()).'</td>';
537 $return .= '<td>'.util_display_user($file->getSubmittedUnixName(), $file->getSubmittedBy(), $file->getSubmittedRealName()).'</td>';
538 $return .= '<td>'.util_make_link('/tracker/download.php/'.$group_id.'/'. $this->getID().'/'. $ah->getID() .'/'.$file->getID().'/'.$file->getName(), htmlspecialchars($file->getName())).'</td>';
539 if (forge_check_perm('tracker', $this->getID(), 'tech')) {
540 $return .= '<td><input type="checkbox" name="delete_file[]" value="'. $file->getID() .'">'._('Delete').'</td>';
545 $return .= $HTML->listTableBottom();
547 $return .= $HTML->information(_('No attached documents'));
555 * @param array $types
556 * @param string $mode
557 * @return string HTML template.
559 function getRenderHTML($types=array(), $mode='') {
560 // Use template only for the browse (not for query or mass update)
561 if (($mode === 'DISPLAY' || $mode === 'DETAIL' || $mode === 'UPDATE' || $mode == 'NEW')
562 && $this->data_array['custom_renderer']) {
563 return preg_replace('/<!--(\S+.*?)-->/','{$\\1}', $this->data_array['custom_renderer']);
565 return $this->generateRenderHTML($types, $mode);
572 * @param array $types
573 * @param string $mode Display mode (QUERY OR DISPLAY OR NEW)
574 * @return string HTML template.
576 function generateRenderHTML($types=array(), $mode) {
577 if ($mode == 'NEW') {
578 $efarr = $this->getExtraFields($types, false, false);
580 $efarr = $this->getExtraFields($types);
582 //each two columns, we'll reset this and start a new row
585 <!-- Start Extra Fields Rendering -->
589 $keys=array_keys($efarr);
591 if ($count == 0) return '';
593 for ($k=0; $k<$count; $k++) {
596 // Do not show the required star in query mode (creating/updating a query).
597 $is_required = ($mode == 'QUERY' || $mode == 'DISPLAY') ? 0 : $efarr[$i]['is_required'];
598 if ($mode == 'QUERY' && ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_DATETIME || $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_DATE)) {
599 $name = sprintf(_('%s range'), $efarr[$i]['field_name']).($is_required ? utils_requiredField() : '')._(': ');
601 $name = $efarr[$i]['field_name'].($is_required ? utils_requiredField() : '')._(': ');
603 $name = '<strong>'.$name.'</strong>';
605 if ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_SELECT ||
606 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX ||
607 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_RADIO ||
608 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT ||
609 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_STATUS ||
610 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_USER ||
611 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_RELEASE ) {
614 <td class="halfwidth top">'.$name.'<br />{$'.$efarr[$i]['field_name'].'}</td>';
616 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_TEXT ||
617 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_INTEGER ||
618 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_RELATION ||
619 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_PARENT ||
620 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_DATETIME ||
621 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_EFFORT) {
623 //text fields might be really wide, so need a row to themselves.
624 if (($col_count == 1) && ($efarr[$i]['attribute1'] > 30)) {
634 <td style="width:'.(50*$colspan).'%" colspan="'.$colspan.'" class="top">'.$name.'{$PostName:'.$efarr[$i]['field_name'].'}<br />{$'.$efarr[$i]['field_name'].'}</td>';
636 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_TEXTAREA) {
638 //text areas might be really wide, so need a row to themselves.
639 if (($col_count == 1) && ($efarr[$i]['attribute2'] > 30)) {
649 <td style="width:'.(50*$colspan).'%" colspan="'.$colspan.'" class="top">'.$name.'{$PostName:'.$efarr[$i]['field_name'].'}<br />{$'.$efarr[$i]['field_name'].'}</td>';
654 //we've done two columns - if there are more to do, start a new row
655 if (($col_count == 2) && ($k != $count-1)) {
662 if ($col_count == 1) {
668 <!-- End Extra Fields Rendering -->';
673 * renderSelect - this function builds pop up box with choices.
675 * @param int $extra_field_id The ID of this field.
676 * @param string $checked The item that should be checked
677 * @param bool|string $show_100 Whether to show the '100 row'
678 * @param string $text_100 What to call the '100 row'
679 * @param bool $show_any
680 * @param string $text_any
681 * @param bool $allowed
682 * @param array $attrs
683 * @return string HTML code for the box and choices
685 function renderSelect($extra_field_id, $checked='xzxz', $show_100=false, $text_100='none', $show_any=false, $text_any='Any', $allowed=false, $attrs = array()) {
686 if ($text_100 == 'none'){
689 $arr = $this->getExtraFieldElements($extra_field_id);
690 $aef = new ArtifactExtraField($this, $extra_field_id);
691 $aefChildren = $aef->getChildren();
692 if (!empty($aefChildren)) {
693 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'with-depcy';
695 if ($aef->isAutoAssign()) {
696 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'autoassign';
700 $opt_attrs = array();
701 $attrs_100 = array();
703 for ($i=0; $i<count($arr); $i++) {
704 $vals[$i]=$arr[$i]['element_id'];
705 $texts[$i]=$arr[$i]['element_name'];
706 $opt_attrs[$i]=array();
707 $aefe = new ArtifactExtraFieldElement($aef, $arr[$i]['element_id']);
708 if (!empty($aefChildren)) {
709 $cElmntArr = $aefe->getChildrenElements();
710 if (!empty($cElmntArr))
713 foreach ($cElmntArr as $key=>$cElmnt) {
714 $childField = new ArtifactExtraField($this, $key);
715 $dependency .= (empty($dependency) ? '':', ').'{"field":'.$key.', "elmnt": ['.implode(', ', $cElmnt).']}';
717 $dependency = '['.$dependency.']';
718 $opt_attrs[$i]= array('data-dependency'=>$dependency);
721 if ($aef->isAutoAssign()) {
722 $autoAssignTo = UserManager::instance()->getUserById($aefe->getAutoAssignto());
723 $opt_attrs[$i]=array_merge(isset($opt_attrs[$i]) ? $opt_attrs[$i] : array(), array('data-autoassignto'=>'{"id":'.$aefe->getAutoAssignto().', "name":"'.$autoAssignTo->getRealName().' ('.$autoAssignTo->getUnixName().')"}'));
727 if ($show_100 && !empty($aefChildren)) {
728 $attrs_100 = array('data-dependency'=>'{"fields": ['.implode(', ', $aefChildren).']}');
730 return html_build_select_box_from_arrays($vals, $texts,'extra_fields['.$extra_field_id.']', $checked, $show_100, $text_100, $show_any, $text_any, $allowed, $attrs, $opt_attrs, $attrs_100);
734 * renderUserField - this function builds pop up box with users.
736 * @param int $extra_field_id The ID of this field.
737 * @param string $checked The item that should be checked
738 * @param bool|string $show_100 Whether to show the '100 row'
739 * @param string $text_100 What to call the '100 row'
740 * @param bool $show_any
741 * @param string $text_any
742 * @param bool $allowed
743 * @param array $attrs
744 * @return string HTML code for the box and choices
746 function renderUserField($extra_field_id, $checked='xzxz', $show_100=false, $text_100='none', $show_any=false, $text_any='Any', $allowed=false, $attrs = array()) {
747 if ($text_100 == 'none' || $text_100 == 'nobody'){
748 $text_100=_('Nobody');
750 $arr = $this->getExtraFieldElements($extra_field_id);
751 $selectedRolesId = array();
752 for ($i=0; $i<count($arr); $i++) {
753 $selectedRolesId[$i]=$arr[$i]['element_name'];
755 $roles = $this->getGroup()->getRoles();
756 $userArray = array();
757 foreach ($roles as $role) {
758 if (in_array($role->getID(), $selectedRolesId)) {
759 foreach ($role->getUsers() as $user) {
760 $userArray[$user->getID()] = $user->getRealName().(($user->getStatus()=='S') ? ' '._('[SUSPENDED]') : '');
764 if (is_integer($checked) && !isset($userArray[$checked])) {
765 $checkedUser = user_get_object($checked);
766 $userArray[$checkedUser->getID()] = $checkedUser->getRealName().' '._('[DELETED]');
768 asort($userArray,SORT_FLAG_CASE | SORT_STRING);
769 $keys = array_keys($userArray);
770 $vals = array_values($userArray);
771 return html_build_select_box_from_arrays($keys, $vals,'extra_fields['.$extra_field_id.']', $checked, $show_100, $text_100, $show_any, $text_any, $allowed, $attrs);
775 * renderMultiUserField - this function builds pop up box with users.
777 * @param int $extra_field_id The ID of this field.
778 * @param string $checked The item that should be checked
779 * @param bool|string $show_100 Whether to show the '100 row'
780 * @param string $text_100 What to call the '100 row'
781 * @param bool $show_any
782 * @param string $text_any
783 * @param bool $allowed
784 * @param array $attrs
785 * @return string HTML code for the box and choices
787 function renderMultiUserField($extra_field_id, $checked='xzxz', $show_100=false, $text_100='none', $show_any=false, $text_any='Any', $allowed=false, $attrs = array()) {
788 if ($text_100 == 'none' || $text_100 == 'nobody'){
789 $text_100=_('Nobody');
795 if (!is_array($checked)) {
796 $checked = explode(',', $checked);
799 $arr = $this->getExtraFieldElements($extra_field_id);
800 $selectedRolesId = array();
801 for ($i=0; $i<count($arr); $i++) {
802 $selectedRolesId[$i]=$arr[$i]['element_name'];
804 $roles = $this->getGroup()->getRoles();
805 $userArray = array();
806 foreach ($roles as $role) {
807 if (in_array($role->getID(), $selectedRolesId)) {
808 foreach ($role->getUsers() as $user) {
809 $userArray[$user->getID()] = $user->getRealName().(($user->getStatus()=='S') ? ' '._('[SUSPENDED]') : '');
813 if (is_integer($checked) && !isset($userArray[$checked])) {
814 $checkedUser = user_get_object($checked);
815 $userArray[$checkedUser->getID()] = $checkedUser->getRealName().' '._('[DELETED]');
817 asort($userArray,SORT_FLAG_CASE | SORT_STRING);
818 $size = min(count($userArray)+1, 15);
819 $keys = array_keys($userArray);
820 $vals = array_values($userArray);
822 return html_build_multiple_select_box_from_arrays($keys, $vals,'extra_fields['.$extra_field_id.'][]', $checked, $size, $show_100, $text_100, $allowed, $attrs);
826 * renderReleaseField - this function builds 2 pop up boxes with packages & releases.
828 * @param int $extra_field_id The ID of this field.
829 * @param string $checked The item that should be checked
830 * @param bool|string $show_100 Whether to show the '100 row'
831 * @param string $text_100 What to call the '100 row'
832 * @param bool $show_any
833 * @param string $text_any
834 * @param bool $allowed
835 * @param array $attrs
836 * @return string HTML code for the box and choices
838 function renderReleaseField($extra_field_id, $checked = 'xzxz', $show_100 = false, $text_100 = 'none', $show_any = false, $text_any = 'Any', $allowed = false, $attrs = array()) {
839 if ($text_100 == 'none'){
843 $releasesArray = array();
844 $releasesAttrs = array();
847 $arr = $this->getExtraFieldElements($extra_field_id);
848 $selectedPackagesId = array();
849 for ($i=0; $i<count($arr); $i++) {
850 $selectedPackagesId[$i]=$arr[$i]['element_name'];
853 $fpFactory = new FRSPackageFactory($this->getGroup());
854 $packages = $fpFactory->getFRSs(true);
855 uasort($packages, 'compareObjectName');
856 foreach ($packages as $package) {
857 $releases = $package->getReleases(false);
858 uasort($releases, 'compareObjectName');
859 foreach ($releases as $release) {
860 $optGroup[] = $package->getName();
861 $releasesArray[$release->getID()] = $release->getName();
865 $keys = array_keys($releasesArray);
866 $vals = array_values($releasesArray);
867 return html_build_select_box_from_arrays($keys, $vals, 'extra_fields['.$extra_field_id.']', $checked, $show_100, $text_100, $show_any, $text_any, $allowed, $attrs, $releasesAttrs, array(), $optGroup);
871 * renderMultiReleaseField - this function builds 2 pop up boxes with packages & releases.
873 * @param int $extra_field_id The ID of this field.
874 * @param string $checked The item that should be checked
875 * @param bool|string $show_100 Whether to show the '100 row'
876 * @param string $text_100 What to call the '100 row'
877 * @param bool $show_any
878 * @param string $text_any
879 * @param bool $allowed
880 * @param array $attrs
881 * @return string HTML code for the box and choices
883 function renderMultiReleaseField($extra_field_id, $checked='xzxz', $show_100=false, $text_100='none', $show_any=false, $text_any='Any', $allowed=false, $attrs=array()) {
884 if ($text_100 == 'none'){
890 if (!is_array($checked)) {
891 $checked = explode(',', $checked);
894 $releasesArray = array();
895 $releasesAttrs = array();
898 $arr = $this->getExtraFieldElements($extra_field_id);
899 $selectedPackagesId = array();
900 for ($i=0; $i<count($arr); $i++) {
901 $selectedPackagesId[$i]=$arr[$i]['element_name'];
904 $packages = get_frs_packages($this->getGroup());
905 uasort($packages, 'compareObjectName');
906 foreach ($packages as $package) {
907 if (in_array($package->getID(), $selectedPackagesId)) {
908 $releases = $package->getReleases();
909 uasort($releases, 'compareObjectName');
910 foreach ($releases as $release) {
911 $optGroup[] = $package->getName();
912 $releasesArray[$release->getID()] = $release->getName();
916 $size = min(count($releasesArray) + count($optGroup) + 1, 15);
917 $keys = array_keys($releasesArray);
918 $vals = array_values($releasesArray);
919 return html_build_multiple_select_box_from_arrays($keys, $vals,'extra_fields['.$extra_field_id.'][]', $checked, $size, $show_100, $text_100, $allowed, $attrs, $releasesAttrs, array(), $optGroup);
923 * renderRadio - this function builds radio buttons.
925 * @param int $extra_field_id The $int ID of this field.
926 * @param string $checked The $string item that should be checked
927 * @param bool $show_100 Whether $string to show the '100 row'
928 * @param string $text_100 What $string to call the '100 row'
929 * @param bool $show_any
930 * @param string $text_any
931 * @param bool $allowed
932 * @param array $attrs Array of other attributes
933 * @return string HTML code using radio buttons
935 function renderRadio($extra_field_id, $checked='xzxz', $show_100=false, $text_100='none', $show_any=false, $text_any='Any', $allowed = false, $attrs = array()) {
937 $arr = $this->getExtraFieldElements($extra_field_id);
938 $aef = new ArtifactExtraField($this, $extra_field_id);
939 $aefChildren = $aef->getChildren();
940 if (!empty($aefChildren)) {
941 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'with-depcy';
946 $radios_attrs = array();
947 $attrs_100 = array();
949 for ($i=0; $i<count($arr); $i++) {
950 $vals[$i]=$arr[$i]['element_id'];
951 $texts[$i]=$arr[$i]['element_name'];
952 $aefe = new ArtifactExtraFieldElement($aef, $arr[$i]['element_id']);
954 if (!empty($aefChildren)) {
955 $cElmntArr = $aefe->getChildrenElements();
956 if (!empty($cElmntArr))
958 foreach ($cElmntArr as $key=>$cElmnt) {
959 $childField = new ArtifactExtraField($this, $key);
960 $dependency .= (empty($dependency) ? '':', ').'{"field":'.$key.', "elmnt": ['.implode(', ', $cElmnt).']}';
962 $dependency = '['.$dependency.']';
963 $radios_attrs[$i]['data-dependency']=$dependency;
966 if ($aef->isAutoAssign()) {
967 $autoAssignTo = UserManager::instance()->getUserById($aefe->getAutoAssignto());
968 $radios_attrs[$i]=array_merge(isset($radios_attrs[$i]) ? $radios_attrs[$i] : array(), array('data-autoassignto'=>'{"id":'.$aefe->getAutoAssignto().', "name":"'.$autoAssignTo->getRealName().' ('.$autoAssignTo->getUnixName().')"}'));
971 if ($show_100 && !empty($aefChildren)) {
972 $attrs_100 = array('data-dependency'=>'{"fields": ['.implode(', ', $aefChildren).']}');
974 return html_build_radio_buttons_from_arrays($vals, $texts,'extra_fields['.$extra_field_id.']', $checked, $show_100, $text_100, $show_any, $text_any, $allowed, $attrs, $radios_attrs, $attrs_100);
978 * renderCheckbox - this function builds checkboxes.
980 * @param int $extra_field_id The ID of this field.
981 * @param array $checked The items that should be checked
982 * @param bool|string $show_100 Whether to show the '100 row'
983 * @param string $text_100 What to call the '100 row'
984 * @param bool $allowed
985 * @param array $attrs Array of other attributes
986 * @return string radio buttons
988 function renderCheckbox($extra_field_id, $checked=array(), $show_100=false, $text_100='none', $allowed=false, $attrs = array()) {
989 if ($text_100 == 'none'){
993 if (!$checked || !is_array($checked)) {
996 if (!empty($attrs['title'])) {
997 $attrs['title'] = util_html_secure($attrs['title']);
1000 $arr = $this->getExtraFieldElements($extra_field_id);
1001 $aef = new ArtifactExtraField($this, $extra_field_id);
1002 $aefChildren = $aef->getChildren();
1003 if (!empty($aefChildren)) {
1004 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'with-depcy';
1009 $chk_attrs = array();
1010 $attrs_100 = array();
1012 for ($i=0; $i<count($arr); $i++) {
1013 $aefe = new ArtifactExtraFieldElement($aef, $arr[$i]['element_id']);
1014 $vals[$i] = $arr[$i]['element_id'];
1015 $texts[$i] = $arr[$i]['element_name'];
1016 $chk_attrs[$i] = array();
1018 if (!empty($aefChildren)) {
1019 $cElmntArr = $aefe->getChildrenElements();
1020 if (!empty($cElmntArr))
1022 foreach ($cElmntArr as $key=>$cElmnt) {
1023 $childField = new ArtifactExtraField($this, $key);
1024 $dependency .= (empty($dependency) ? '':', ').'{"field":'.$key.', "elmnt": ['.implode(', ', $cElmnt).']}';
1026 $dependency = '['.$dependency.']';
1027 $chk_attrs[$i]['data-dependency']=$dependency;
1031 if ($show_100 && !empty($aefChildren)) {
1032 $attrs_100['data-dependency'] ='{"fields": ['.implode(', ', $aefChildren).']}';
1034 return html_build_checkboxes_from_arrays($vals, $texts,'extra_fields['.$extra_field_id.']', $checked,false, $show_100, $text_100, $allowed, $attrs, $chk_attrs, $attrs_100);
1038 * renderMultiSelectBox - this function builds checkboxes.
1040 * @param int $extra_field_id The ID of this field.
1041 * @param array $checked The items that should be checked
1042 * @param bool|string $show_100 Whether to show the '100 row'
1043 * @param string $text_100 What to call the '100 row'
1044 * @param bool $allowed
1045 * @param array $attrs Array of other attributes
1046 * @return string radio multiselectbox
1048 function renderMultiSelectBox($extra_field_id, $checked=array(), $show_100=false, $text_100='none', $allowed=false, $attrs=array()) {
1052 if (!is_array($checked)) {
1053 $checked = explode(',', $checked);
1055 $arr = $this->getExtraFieldElements($extra_field_id);
1056 $aef = new ArtifactExtraField($this, $extra_field_id);
1057 $aefChildren = $aef->getChildren();
1058 if (!empty($aefChildren)) {
1059 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'with-depcy';
1063 $opt_attrs = array();
1064 $attrs_100 = array();
1066 for ($i=0; $i<count($arr); $i++) {
1067 $vals[$i]=$arr[$i]['element_id'];
1068 $texts[$i]=$arr[$i]['element_name'];
1069 $aefe = new ArtifactExtraFieldElement($aef, $arr[$i]['element_id']);
1070 if (!empty($aefChildren)) {
1071 $cElmntArr = $aefe->getChildrenElements();
1072 if (!empty($cElmntArr))
1075 foreach ($cElmntArr as $key=>$cElmnt) {
1076 $childField = new ArtifactExtraField($this, $key);
1077 $dependency .= (empty($dependency) ? '':', ').'{"field":'.$key.', "elmnt": ['.implode(', ', $cElmnt).']}';
1079 $dependency = '['.$dependency.']';
1080 $opt_attrs[$i]= array('data-dependency'=>$dependency);
1084 $size = min(count($arr)+1, 15);
1085 if ($show_100 && !empty($aefChildren)) {
1086 $attrs_100 = array('data-dependency'=>'{"fields": ['.implode(', ', $aefChildren).']}');
1088 return html_build_multiple_select_box_from_arrays($vals, $texts,"extra_fields[$extra_field_id][]", $checked, $size, $show_100, $text_100, $allowed, $attrs, $opt_attrs, $attrs_100);
1092 * renderTextField - this function builds a text field.
1094 * @param int $extra_field_id The ID of this field.
1095 * @param string $contents The data for this field.
1096 * @param string $size
1097 * @param string $maxlength
1098 * @param array $attrs Array of other attributes
1099 * @return string HTML code of corresponding input tag.
1101 function renderTextField($extra_field_id, $contents, $size, $maxlength, $attrs = array()) {
1102 return html_e('input', array_merge(array('type'=>'text', 'name'=>'extra_fields['.$extra_field_id.']', 'value'=>$contents, 'size'=>$size, 'maxlength'=>$maxlength), $attrs));
1106 * renderIntegerField - this function builds a text field.
1108 * @param int $extra_field_id The ID of this field.
1109 * @param string $contents The data for this field.
1110 * @param string $size
1111 * @param string $maxlength
1112 * @param array $attrs Array of other attributes
1113 * @return string HTML code of corresponding input tag.
1115 function renderIntegerField($extra_field_id, $contents, $size, $maxlength, $attrs = array()) {
1116 $intAttrs = array('type'=>'number', 'name'=>'extra_fields['.$extra_field_id.']', 'value'=>$contents, 'size'=>$size, 'maxlength'=>$maxlength, 'min'=>0);
1117 $newattrs = array_merge($intAttrs, $attrs);
1118 return html_e('input', $newattrs);
1122 * renderRelationField - this function builds a relation field.
1124 * @param int $extra_field_id The ID of this field.
1125 * @param string $contents The data for this field.
1126 * @param string $size
1127 * @param string $maxlength
1128 * @param array $attrs Array of other attributes
1129 * @return string text area and data.
1131 function renderRelationField($extra_field_id, $contents, $size, $maxlength, $attrs = array()) {
1132 $arr = $this->getExtraFieldElements($extra_field_id);
1133 for ($i=0; $i<count($arr); $i++) {
1134 $keys[$i]=$arr[$i]['element_id'];
1135 $vals[$i]=$arr[$i]['element_name'];
1137 // Convert artifact id to links.
1138 $attrsTxt = array();
1139 if (isset($attrs['form'])) {
1140 $attrsTxt['form'] = $attrs['form'];
1142 $html_contents = preg_replace_callback('/\b(\d+)\b/', create_function('$matches', 'return _artifactid2url($matches[1], \'title\');'), $contents);
1143 $edit_contents = $this->renderTextField ($extra_field_id, $contents, $size, $maxlength, $attrsTxt);
1144 return html_e('div',array_merge(array('id'=>'edit'.$extra_field_id, 'style'=>'display: none', 'title'=>_('Tip: Enter a space-separated list of artifact ids ([#NNN] also accepted)')), $attrs), $edit_contents)
1145 .html_e('div',array_merge(array('id'=>'show'.$extra_field_id, 'style'=>'display: block'), $attrs), $html_contents);
1149 * renderParentField - this function builds a parent field.
1151 * @param int $extra_field_id The ID of this field.
1152 * @param string $contents The data for this field.
1153 * @param string $size
1154 * @param string $maxlength
1155 * @param array $attrs Array of other attributes
1156 * @return string text area and data.
1158 function renderParentField($extra_field_id, $contents, $size, $maxlength, $attrs = array()) {
1160 $arr = $this->getExtraFieldElements($extra_field_id);
1161 for ($i=0; $i<count($arr); $i++) {
1162 $keys[$i]=$arr[$i]['element_id'];
1163 $vals[$i]=$arr[$i]['element_name'];
1165 $attrsTxt = array();
1166 if (isset($attrs['form'])) {
1167 $attrsTxt['form'] = $attrs['form'];
1169 if (is_object($ah)) {
1170 $attrsTxt['pattern']='^(?!'.$ah->getID().'$)\d*$';
1172 $attrsTxt['pattern']='^\d*$';
1174 // Convert artifact id to links.
1175 $html_contents = preg_replace_callback('/\b(\d+)\b/', create_function('$matches', 'return _artifactid2url($matches[1], \'title\');'), $contents);
1176 $edit_contents = $this->renderTextField ($extra_field_id, $contents, $size, $maxlength, $attrsTxt);
1177 return html_e('div',array_merge(array('id'=>'edit'.$extra_field_id, 'style'=>'display: none', 'title'=>_('Tip: Enter a space-separated list of artifact ids ([#NNN] also accepted)')), $attrs), $edit_contents)
1178 .html_e('div',array_merge(array('id'=>'show'.$extra_field_id, 'style'=>'display: block'), $attrs), $html_contents);
1182 * renderTextArea - this function builds a text area.
1184 * @param int $extra_field_id The ID of this field.
1185 * @param string $contents The data for this field.
1186 * @param string $rows
1187 * @param string $cols
1188 * @param array $attrs Array of other attributes
1189 * @return string text area and data.
1191 function renderTextArea($extra_field_id, $contents, $rows, $cols, $attrs = array()) {
1192 return html_e('textarea', array_merge(array('name'=>'extra_fields['.$extra_field_id.']', 'rows'=>$rows, 'cols'=>$cols), $attrs), $contents, false);
1196 * renderDatetime - this function builds a Datetime field.
1198 * @param int $extra_field_id The ID of this field.
1199 * @param string $datetime datetime for this field.
1200 * @param array $attrs Array of other attributes
1201 * @return string datetime.
1203 function renderDatetime($extra_field_id, $datetime, $attrs = array()) {
1204 if (!$datetime=='') {
1205 $datetime_format = _('Y-m-d H:i');
1206 $datetime = date($datetime_format, $datetime);
1208 if (isset($attrs['class'])) {
1209 $attrs['class'] = $attrs['class'] . ' datetimepicker';
1211 $attrs['class'] = 'datetimepicker';
1213 return html_e('input', array_merge(array('type'=>'text', 'name'=>'extra_fields['.$extra_field_id.']', 'value'=>$datetime), $attrs));
1216 function renderDateRange($extra_field_id, $dateRange, $attrs = array()) {
1217 // http://html5pattern.com/Dates
1218 // Date with leapyear-check
1219 $datepattern = '(?:19|20)(?:(?:[13579][26]|[02468][048])-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-9])|(?:(?!02)(?:0[1-9]|1[0-2])-(?:30))|(?:(?:0[13578]|1[02])-31))|(?:[0-9]{2}-(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:(?!02)(?:0[1-9]|1[0-2])-(?:29|30))|(?:(?:0[13578]|1[02])-31)))';
1220 return html_e('input', array_merge(array('type'=>'text', 'name'=>'extra_fields['.$extra_field_id.']', 'pattern'=>$datepattern.' '.$datepattern, 'maxlength'=>21, 'size'=>21, 'value'=>$dateRange), $attrs));
1224 * renderEffort - this function builds a Effort extra field.
1226 * @param int $extra_field_id The ID of this field.
1227 * @param string $contents The data for this field.
1228 * @param string $size
1229 * @param string $maxlength
1230 * @param array $attrs Array of other attributes
1231 * @return string text area and data.
1233 function renderEffort($extra_field_id, $contents, $size, $maxlength, $attrs = array()) {
1234 $effortUnitSet = New EffortUnitSet($this, $this->getEffortUnitSet());
1235 $effortUnitFactory = New EffortUnitFactory($effortUnitSet);
1236 $units = $effortUnitFactory->getUnits();
1240 foreach ($units as $unit) {
1241 $vals [] = $unit->getID();
1242 $texts [] = $unit->getName();
1243 $opts_attrs []['data-factor'] = $unit->getConversionFactorForBaseUnit();
1245 $valueInUnitBase = $effortUnitFactory->encodedToValueInBaseUnit($contents);
1246 $value = $effortUnitFactory->encodedToValue($contents);
1247 $unitId = $effortUnitFactory->encodedToUnitId($contents);
1248 if (isset($attrs['class'])) {
1249 $attrs['class'] .= ' effort';
1251 $attrs['class'] = 'effort';
1253 $attrs['data-effortid'] = $extra_field_id;
1254 $hiddenAttrs = array();
1255 if (isset($attrs['form'])) {
1256 $hiddenAttrs['form'] = $attrs['form'];
1258 $return = html_e('input', array_merge(array('type'=>'hidden', 'name'=>'extra_fields['.$extra_field_id.']', 'value'=>$valueInUnitBase.'U'.$unitId), $hiddenAttrs));
1259 $return .= html_e('input', array_merge(array('type'=>'number', 'name'=>'value['.$extra_field_id.']', 'value'=>$value, 'size'=>$size, 'maxlength'=>$maxlength, 'min'=>0), $attrs));
1260 $return .= html_build_select_box_from_arrays($vals, $texts, 'unit['.$extra_field_id.']', $unitId, false, '', false, '', false, $attrs, $opts_attrs);
1264 function renderEffortRange($extra_field_id, $contents, $size, $maxlength, $attrs = array()) {
1265 $effortUnitSet = New EffortUnitSet($this, $this->getEffortUnitSet());
1266 $effortUnitFactory = New EffortUnitFactory($effortUnitSet);
1267 $units = $effortUnitFactory->getUnits();
1270 $opts_attrs = array();
1271 foreach ($units as $unit) {
1272 $vals [] = $unit->getID();
1273 $texts [] = $unit->getName();
1274 $opts_attrs []['data-factor'] = $unit->getConversionFactorForBaseUnit();
1276 $contentsFrom = '0U'.$vals[0];
1277 $contentsTo = '0U'.$vals[0];
1278 if (preg_match('/^(\d+U\d+) ?(\d+U\d+)?$/', $contents, $matches)) {
1279 $contentsFrom = $matches[1];
1280 $contentsTo = $matches[2];
1282 $valueInUnitBaseFrom = $effortUnitFactory->encodedToValueInBaseUnit($contentsFrom);
1283 $valueInUnitBaseTo = $effortUnitFactory->encodedToValueInBaseUnit($contentsTo);
1284 $valueFrom = $effortUnitFactory->encodedToValue($contentsFrom);
1285 $valueTo = $effortUnitFactory->encodedToValue($contentsTo);
1286 $unitIdFrom = $effortUnitFactory->encodedToUnitId($contentsFrom);
1287 $unitIdTo = $effortUnitFactory->encodedToUnitId($contentsTo);
1288 $attrs['data-effortid'] = $extra_field_id;
1289 if (isset($attrs['class'])) {
1290 $attrs['class'] .= ' effort-range';
1292 $attrs['class'] = 'effort-range';
1294 $attrsFrom = $attrs;
1296 $hiddenAttrs = array();
1297 if (isset($attrs['form'])) {
1298 $hiddenAttrs['form'] = $attrs['form'];
1300 $return = html_e('input', array_merge(array('type'=>'hidden', 'name'=>'extra_fields['.$extra_field_id.']', 'value'=>$valueInUnitBaseFrom.'U'.$unitIdFrom.' '.$valueInUnitBaseTo.'U'.$unitIdTo), $hiddenAttrs));
1301 $return .= _('Between')._(':').html_e('br');
1302 $return .= html_e('input', array_merge(array('type'=>'number', 'name'=>'value_from['.$extra_field_id.']', 'value'=>$valueFrom, 'size'=>$size, 'maxlength'=>$maxlength, 'min'=>0), $attrsFrom));
1303 $return .= html_build_select_box_from_arrays($vals, $texts, 'unit_from['.$extra_field_id.']', $unitIdFrom, false, '', false, '', false, $attrsFrom, $opts_attrs);
1304 $return .= html_e('br');
1305 $return .= _('and')._(':').html_e('br');
1306 $return .= html_e('input', array_merge(array('type'=>'number', 'name'=>'value_to['.$extra_field_id.']', 'value'=>$valueTo, 'size'=>$size, 'maxlength'=>$maxlength, 'min'=>0), $attrsTo));
1307 $return .= html_build_select_box_from_arrays($vals, $texts, 'unit_to['.$extra_field_id.']', $unitIdTo, false, '', false, '', false, $attrsTo, $opts_attrs);
1312 * renderFormulaField - this function builds a formula field (RO).
1314 * @param string $contents The data for this field.
1317 function renderFormulaField($contents) {
1322 * renderSLAField - this function builds a formula field (RO).
1324 * @param int $extra_field_id The ID of this field.
1325 * @param string $contents The data for this field.
1329 function renderSLAField($extra_field_id, $contents, $ef) {
1332 if (!isset($aid) ||!$aid) {
1336 $config = parse_ini_string($ef["attributes"]);
1337 $field_name = $config['field'];
1338 $rule = explode(':', $config['values']);
1340 if (!isset($config['start_id']) || $aid >= $config['start_id']) {
1341 // Get Max value from
1342 // @todo use global variable $aid !!!
1343 $artifact = artifact_get_object($aid);
1344 $value = $artifact->getFieldDataByKey('alias', $field_name);
1345 $field = $artifact->getFieldTypeByKey('alias', $field_name);
1346 $elements = $artifact->ArtifactType->getExtraFieldElements($field['extra_field_id']);
1347 for ($i = 0; $i < count($elements); $i++) {
1348 if ($elements[$i]['element_name'] == $value) {
1349 $max = 60 * $rule[$i];
1355 $value = '<span class="label label-warning">Not Set</span>';
1356 } elseif ($ef['alias'] == 'response_time') {
1357 // Implement Response SLA: Time from create to first comment.
1358 $messages = $artifact->getMessageObjects();
1359 if (count($messages) > 0) {
1360 $message = $messages[count($messages) - 1];
1361 $current_time = $message->getAddDate();
1362 $timer_is_running = false;
1363 } elseif ($artifact->getStatusID() == 2) {
1364 $current_time = $artifact->getCloseDate();
1365 $timer_is_running = false;
1367 $current_time = time();
1368 $timer_is_running = true;
1370 $time = $current_time - $artifact->getOpenDate();
1371 $percent = round(100 * $time / $max);
1372 $remaining = $max - $time;
1373 if ($remaining >= 0) {
1374 $value = gmdate("G\h i", $remaining);
1376 $value = '<b>-' . gmdate("G\h i", -$remaining) . "</b>";
1379 if (!$timer_is_running) {
1380 if ($remaining >= 0) {
1381 $value = '<span class="label label-success">' . _('OK') . '</span>';
1383 $value = '<span class="label label-danger">' . _('Missed') . '</span>';
1386 if ($percent > 90) {
1387 $value = '<span class="sla-alert">' . $value . '</span>';
1388 } elseif ($percent > 80) {
1389 $value = '<span class="sla-warning">' . $value . '</span>';
1391 $value = '<span class="sla-normal">' . $value . '</span>';
1394 } elseif ($ef['alias'] == 'resolution_time') {
1395 // Implement Resolution SLA: Time from create to closed state.
1396 list($last_status_change, $timer, $timer_is_running) = json_decode($contents);
1399 if ($timer_is_running) {
1400 $time += time() - $last_status_change;
1403 $percent = round(100 * $time / $max);
1404 $remaining = $max - $time;
1405 if ($remaining >= 0) {
1406 $value = gmdate("G\h i", $remaining);
1408 $value = '<b>-' . gmdate("G\h i", -$remaining) . "</b>";
1410 if ($artifact->getStatusID() == 2) {
1411 if ($remaining >= 0) {
1412 $value = '<span class="label label-success">' . _('OK') . '</span>';
1414 $value = '<span class="label label-danger">' . _('Missed') . '</span>';
1417 if (!$timer_is_running) {
1418 $value = ' <span class="label label-info">' . _('Suspended') . '</span>';
1419 } elseif ($percent > 90) {
1420 $value = '<span class="sla-alert">' . $value . '</span>';
1421 } elseif ($percent > 80) {
1422 $value = '<span class="sla-warning">' . $value . '</span>';
1424 $value = '<span class="sla-normal">' . $value . '</span>';
1429 $value = '<span class="label label-default">' . _('Not Available') . '</span>';
1434 function technicianBox($name = 'assigned_to[]', $checked = 'xzxz', $show_100 = true, $text_100 = 'none', $extra_id = '-1', $extra_name = '', $multiple = false, $attrs = array()) {
1435 if ($text_100=='none'){
1436 $text_100=_('Nobody');
1438 $engine = RBACEngine::getInstance();
1439 $techs = $engine->getUsersByAllowedAction('tracker', $this->getID(), 'tech') ;
1444 sortUserList($techs);
1446 foreach ($techs as $tech) {
1447 $ids[] = $tech->getID() ;
1448 $names[] = $tech->getRealName() ;
1451 if ($extra_id != '-1') {
1453 $names[]=$extra_name;
1457 if (!is_array($checked)) {
1458 $checked = explode(',', $checked);
1460 $size = min(count($ids)+1, 15);
1461 return html_build_multiple_select_box_from_arrays($ids, $names, $name, $checked, $size, $show_100, $text_100, false, $attrs);
1463 return html_build_select_box_from_arrays($ids, $names, $name, $checked, $show_100, $text_100, false, false, false, $attrs);
1467 function submitterBox($name = 'submitted_by[]', $checked = 'xzxz', $show_100 = true, $text_100 = 'none', $extra_id = '-1', $extra_name = '', $multiple = false, $attrs = array()) {
1468 if ($text_100=='none'){
1469 $text_100=_('Nobody');
1471 $result = $this->getSubmitters();
1472 $ids =& util_result_column_to_array($result,0);
1473 $names =& util_result_column_to_array($result,1);
1474 if ($extra_id != '-1') {
1476 $names[]=$extra_name;
1480 if (!is_array($checked)) {
1481 $checked = explode(',', $checked);
1483 $size = min(count($ids)+1, 15);
1484 return html_build_multiple_select_box_from_arrays($ids, $names, $name, $checked, $size, $show_100, $text_100, false, $attrs);
1486 return html_build_select_box_from_arrays($ids, $names, $name, $checked, $show_100, $text_100, false, $attrs);
1490 function lastModifierBox($name='last_modified_by[]', $checked='xzxz', $show_100=true, $text_100='none', $extra_id='-1', $extra_name='', $multiple=false) {
1491 if ($text_100=='none'){
1492 $text_100=_('Nobody');
1494 $result = $this->getLastModifiers();
1495 $ids =& util_result_column_to_array($result,0);
1496 $names =& util_result_column_to_array($result,1);
1497 if ($extra_id != '-1') {
1499 $names[]=$extra_name;
1503 if (!is_array($checked)) {
1504 $checked = explode(',', $checked);
1506 $size = min(count($ids)+1, 15);
1507 return html_build_multiple_select_box_from_arrays($ids, $names, $name, $checked, $size, $show_100, $text_100);
1509 return html_build_select_box_from_arrays($ids, $names, $name, $checked, $show_100, $text_100);
1513 function cannedResponseBox($name = 'canned_response', $checked = 'xzxz', $attrs = array()) {
1514 if (!isset($attrs['id'])) {
1515 $attrs['id'] = $name;
1517 return html_build_select_box($this->getCannedResponses(), $name, $checked, true, 'none', false, '', false, $attrs);
1521 * statusBox - show the statuses - automatically shows the "custom statuses" if they exist
1523 * @param string $name
1524 * @param string $checked
1525 * @param bool $show_100
1526 * @param string $text_100
1527 * @param array $attrs
1528 * @return string HTML code
1530 function statusBox($name = 'status_id', $checked = 'xzxz', $show_100 = false, $text_100 = 'none', $attrs = array()) {
1531 if ($text_100=='none'){
1532 $text_100=_('None');
1534 return html_build_select_box($this->getStatuses(), $name, $checked, $show_100, $text_100, false, '', false, $attrs);
1538 * priorityBox - show the priorities
1540 * @param string $name
1541 * @param string $checked_val
1542 * @param bool $nochange
1543 * @param array $attrs
1544 * @param bool $show_any
1545 * @return string HTML code
1547 function priorityBox($name = 'priority', $checked_val = '3', $nochange = false, $attrs = array(), $show_any = false){
1548 return html_build_priority_select_box($name, $checked_val, $nochange, $attrs, $show_any);
1551 function javascript() {
1553 var invalidSelectMsg = '"._("One or more of the selected options is not allowed")."';
1554 var invalidInputMsg = '". _("This choice is not allowed")."';
1555 var groupId =".$this->Group->getID().";
1556 var atId = ".$this->getID().";";
1557 $effortUnitSet = New EffortUnitSet($this, $this->getEffortUnitSet());
1558 if ($effortUnitSet->isAutoconvert()) {
1559 $jseffort = <<<'EOS'
1560 $("select.effort").change(function(){
1561 var effortid = $(this).data("effortid");
1562 var value = parseInt(parseInt($("input[name='extra_fields["+effortid+"]']").val())/$("select[name='unit["+effortid+"]'] option:selected").data('factor'));
1563 $("input[name='value["+effortid+"]']").val(value);
1564 $("input[name='extra_fields["+effortid+"]']").val(value*$("select[name='unit["+effortid+"]'] option:selected").data('factor')+'U'+$("select[name='unit["+effortid+"]']").val());
1566 $("input.effort").change(function(){
1567 var effortid = $(this).data("effortid");
1568 var value = $("input[name='value["+effortid+"]']").val();
1569 $("input[name='extra_fields["+effortid+"]']").val(value*$("select[name='unit["+effortid+"]'] option:selected").data('factor')+'U'+$("select[name='unit["+effortid+"]']").val());
1571 $(".effort-range").change(function(){
1572 var effortid = $(this).data("effortid");
1573 $("input[name='extra_fields["+effortid+"]']").val($("input[name='value_from["+effortid+"]']").val()*$("select[name='unit_from["+effortid+"]'] option:selected").data('factor')+'U'+$("select[name='unit_from["+effortid+"]']").val()+' '+$("input[name='value_to["+effortid+"]']").val()*$("select[name='unit_to["+effortid+"]'] option:selected").data('factor')+'U'+$("select[name='unit_to["+effortid+"]']").val());
1577 $jseffort = <<<'EOS'
1578 $(".effort").change(function(){
1579 var effortid = $(this).data("effortid");
1580 $("input[name='extra_fields["+effortid+"]']").val($("input[name='value["+effortid+"]']").val()*$("select[name='unit["+effortid+"]'] option:selected").data('factor')+'U'+$("select[name='unit["+effortid+"]']").val());
1582 $(".effort-range").change(function(){
1583 var effortid = $(this).data("effortid");
1584 $("input[name='extra_fields["+effortid+"]']").val($("input[name='value_from["+effortid+"]']").val()*$("select[name='unit_from["+effortid+"]'] option:selected").data('factor')+'U'+$("select[name='unit_from["+effortid+"]']").val()+' '+$("input[name='value_to["+effortid+"]']").val()*$("select[name='unit_to["+effortid+"]'] option:selected").data('factor')+'U'+$("select[name='unit_to["+effortid+"]']").val());
1588 $javascript = <<<'EOS'
1589 function showMessage( msg_text, msg_class) {
1590 $("#maindiv h1").append($("<p>", { "class": msg_class }).html( msg_text )).show();
1592 $.expr[':'].invalid = function(elem, index, match) {
1593 var invalids = document.querySelectorAll(':invalid'), result = false, len = invalids.length;
1595 for (var i=0; i<len; i++) {
1596 if (elem === invalids[i]) {
1605 $("input[type='radio'].readonly, input[type='checkbox'].readonly").on('click', function(){
1607 }).on('keydown', function(event){
1608 if(event.keyCode !== 9) return false;
1610 $(".in-formula").change(function(){
1614 data: 'rtype=ajax&function=get_formulas_results&group_id='+groupId+'&atid='+atId+'&status='+$("select[name='status_id'] option:selected").text()+'&assigned_to='+$("select[name='assigned_to'] option:selected").text()+'&'+$("[name^='extra_fields'], #tracker-summary, #tracker-description, [name='priority']").serialize(),
1617 success: function(answer){
1618 if(answer['message']) {
1619 showMessage(answer['message'], 'error');
1621 fields = answer['fields'];
1622 $.each(fields, function (index, field) {
1623 if (field.error!=null){
1624 showMessage(field.error, 'error');
1626 fieldObj = $("[name^='extra_fields["+field.id+"]']");
1627 if (fieldObj.is("input[type='checkbox']")){
1628 fieldObj.each(function() {
1630 for (var key in field.value) {
1631 if (field.value[key] == $(this).val()) {
1636 if (in_array > -1) {
1637 $(this).prop("checked",true);
1639 $(this).prop("checked",false);
1642 } else if (fieldObj.is("input[type='radio']")){
1643 fieldObj.each(function() {
1645 for (var key in field.value) {
1646 if (field.value[key] == $(this).val()) {
1651 if (in_array > -1) {
1652 $(this).prop("checked",true);
1654 $(this).prop("checked",false);
1657 } else if (fieldObj.is("input")){
1658 fieldObj.val(field.value);
1659 } else if (fieldObj.is("select")){
1660 fieldObj.val(field.value);
1661 } else if (fieldObj.is("textarea")){
1662 fieldObj.val(field.value);
1670 $("img.addparent").click(function(){
1674 data: 'rtype=ajax&function=add_parent&group_id='+groupId+'&atid='+atId+'&aid='+$("input#aid").val()+'&parent_id='+$("input#parent_id").val(),
1677 success: function(answer){
1678 if(answer['message']) {
1679 showMessage(answer['message'], 'error');
1681 $("table.parent").replaceWith(answer['parent']);
1682 $("input[name='extra_fields["+answer['parent_efid']+"]']").val(answer['parent_id']);
1683 $("div#show"+answer['parent_efid']).html(answer['parent_link']);
1684 $("input#parent_id").val('');
1685 $("div.addparent").addClass('hide');
1686 $("img.removeparent").click(function(){
1687 removeParent($(this).data("id"));
1695 $("img.addchild").click(function(){
1699 data: 'rtype=ajax&function=add_child&group_id='+groupId+'&atid='+atId+'&aid='+$("input#aid").val()+'&child_id='+$("input#child_id").val(),
1702 success: function(answer){
1703 if(answer['message']) {
1704 showMessage(answer['message'], 'error');
1706 $("table.children").replaceWith(answer['children']);
1707 $("input#child_id").val('');
1708 $("img.removechild").click(function(){
1709 removeChild($(this).data("id"));
1718 $("img.removeparent").click(function(){
1719 removeParent($(this).data("id"));
1722 function removeParent(id) {
1726 data: 'rtype=ajax&function=remove_parent&group_id='+groupId+'&atid='+atId+'&aid='+$("input#aid").val()+'&parent_id='+id,
1729 success: function(answer){
1730 if(answer['message']) {
1731 showMessage(answer['message'], 'error');
1733 $("table#parent"+id).remove();
1734 $("div.addparent").removeClass('hide');
1741 $("img.removechild").click(function(){
1742 removeChild($(this).data("id"));
1745 function removeChild(id) {
1749 data: 'rtype=ajax&function=remove_child&group_id='+groupId+'&atid='+atId+'&aid='+$("input#aid").val()+'&child_id='+id,
1752 success: function(answer){
1753 if(answer['message']) {
1754 showMessage(answer['message'], 'error');
1756 $("span#child"+id).remove();
1762 $(".autoassign[name^='extra_fields']").change(function(){
1763 if ($(this).prop('tagName') == 'SELECT') {
1764 var elmnts = $(this).children('option:selected');
1766 var elmnts = $(this).siblings('input:checked');
1768 elmnts.each(function(i){
1769 var aat = $(this).data("autoassignto");
1770 $("select#tracker-assigned_to option[value="+aat.id+"]").prop('selected', true);
1771 $("span#tracker-assigned_to").text(aat.name);
1774 $(".with-depcy[name^='extra_fields']").change(function(){
1775 if ($(this).prop('tagName') == 'SELECT') {
1776 var elmnts = $(this).children('option:selected');
1778 var elmnts = $(this).siblings('input:checked');
1780 elmnts.each(function(i){
1781 var dep = $(this).data("dependency");
1782 if (this.value!='100') {
1783 $(dep).each(function(j, val) {
1784 $("select[name^='extra_fields["+val.field+"]']:invalid, input[name^='extra_fields["+val.field+"]']:invalid").each(function() {
1785 this.setCustomValidity("");
1786 $(this).off("change.invalid");
1788 $("select[name^='extra_fields["+val.field+"]'] option").each(function(k,opt){
1789 if (this.value!='100') {
1790 if ($.inArray(parseInt(this.value),val.elmnt)>-1) {
1791 $(this).prop('disabled', false).removeClass('option_disabled');
1793 $(this).prop('disabled', true);
1794 $(this).addClass('option_disabled');
1798 $("input[name^='extra_fields["+val.field+"]']").each(function(k,opt){
1799 if (this.value!='100') {
1800 if ($.inArray(parseInt(this.value),val.elmnt)>-1) {
1801 $(this).prop('disabled', false).removeClass($(this).attr('type')+'_disabled');
1803 $(this).prop('disabled', true);
1804 $(this).addClass($(this).attr('type')+'_disabled');
1810 $(dep.fields).each(function(j, val) {
1811 $("select[name^='extra_fields["+val+"]']:invalid, input[name^='extra_fields["+val+"]']:invalid").each(function() {
1812 this.setCustomValidity("");
1814 $("select[name^='extra_fields["+val+"]'] option.option_disabled").each(function() {
1815 $(this).prop('disabled', false).removeClass('option_disabled');
1817 $("input.radio_disable[name^='extra_fields["+val+"]']").each(function() {
1818 $(this).prop('disabled', false).removeClass('radio_disabled');
1820 $("input.checkbox_disabled[name^='extra_fields["+val+"]']").each(function() {
1821 $(this).prop('disabled', false).removeClass('checkbox_disabled');
1826 $("select[name^='extra_fields'] option:selected:disabled").parent().each(function() {
1827 $(this).children('option:selected:disabled').prop('disabled', false);
1828 this.setCustomValidity(invalidSelectMsg);
1829 $(this).on("change.invalid", function() {
1830 $(this).children('option.option_disabled:not(:disabled):not(:selected)').prop('disabled', true);
1831 if (!$(this).children('option.option_disabled:selected').length) {
1832 this.setCustomValidity("");
1833 $(this).off("change.invalid");
1837 $("input[name^='extra_fields']:checked:disabled").each(function() {
1838 $(this).prop('disabled', false);
1839 this.setCustomValidity(invalidInputMsg);
1840 if ($(this).attr('type') == 'radio') {
1841 $(this).siblings('input[type="radio"]').on("change.invalid", function() {
1842 $(this).siblings('input[type="radio"]:invalid').prop('disabled', true).addClass('input_disabled').each(function() {
1843 this.setCustomValidity("");
1845 $(this).siblings('input[type="radio"]').off("change.invalid");
1846 $(this).off("change.invalid");
1849 $(this).on("change.invalid", function() {
1850 $(this).prop('disabled', true);
1851 this.setCustomValidity("");
1852 $(this).off("change.invalid");
1858 return html_e('script', array('type'=>'text/javascript'), '//<![CDATA['."\n".'$(function(){'.$jsvariable."\n".$jseffort."\n".$javascript.'});'."\n".'//]]>');
1865 // c-file-style: "bsd"