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();
131 $links_arr[]='/tracker/admin/?group_id='.$group_id;
132 $title_arr[]=_('New Tracker');
133 $attr_arr[] = array('title'=>_('Create a new tracker.'));
135 $links_arr[]='/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&update_type=1';
136 $title_arr[]=_('Update Settings');
137 $attr_arr[] = array('title'=>_('Set up preferences like expiration times, email addresses.'));
139 $links_arr[]='/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&effort_units=1';
140 $title_arr[]=_('Manage Effort Units');
141 $attr_arr[] = array('title'=>_('Manage Effort Units for Effort custom extra field.'));
143 $links_arr[]='/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&add_extrafield=1';
144 $title_arr[]=_('Manage Custom Fields');
145 $attr_arr[] = 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.'));
147 $links_arr[]='/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&workflow=1';
148 $title_arr[]=_('Manage Workflow');
149 $attr_arr[] = array('title'=>_('Edit tracker workflow.'));
151 $links_arr[]='/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&customize_list=1';
152 $title_arr[]=_('Customize List');
153 $attr_arr[] = array('title'=>_('Customize display for the tracker.'));
155 $links_arr[]='/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&add_canned=1';
156 $title_arr[]=_('Manage Canned Responses');
157 $attr_arr[] = array('title'=>_('Create/change generic response messages for the tracker.'));
159 $links_arr[]='/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&clone_tracker=1';
160 $title_arr[]=_('Apply Template Tracker');
161 $attr_arr[] = array('title'=>_('Duplicate parameters and fields from a template trackers in this one.'));
163 $links_arr[]='/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&delete=1';
164 $title_arr[]=_('Delete');
165 $attr_arr[] = array('title'=>_('Permanently delete this tracker.'));
167 echo $HTML->printSubMenu($title_arr, $links_arr, $attr_arr);
170 function adminFooter($params) {
171 $this->footer($params);
174 function renderSubmitInstructions() {
175 $msg = $this->getSubmitInstructions();
176 return str_replace("\n","<br />", $msg);
179 function renderBrowseInstructions() {
180 $msg = $this->getBrowseInstructions();
181 return str_replace("\n","<br />", $msg);
185 * renderExtraFields - ???
187 * @param array $selected
188 * @param bool $show_100 Display the specific '100' value. Default is false.
189 * @param string $text_100 Label displayed for the '100' value. Default is 'none'
190 * @param bool $show_any
191 * @param string $text_any
192 * @param array $types
193 * @param bool $status_show_100 Force display of the '100' value if needed. Default is false.
194 * @param string $mode QUERY, DISPLAY, UPDATE, NEW
196 function renderExtraFields($selected = array(),
197 $show_100 = false, $text_100 = 'none',
198 $show_any = false, $text_any = 'Any',
200 $status_show_100 = false,
202 if ($mode == 'NEW') {
203 $efarr = $this->getExtraFields($types, false, false);
205 $efarr = $this->getExtraFields($types);
207 //each two columns, we'll reset this and start a new row
208 $template = $this->getRenderHTML($types, $mode);
210 if ($mode=='QUERY') {
211 $keys=array_keys($efarr);
212 for ($k=0; $k<count($keys); $k++) {
214 $type = $efarr[$i]['field_type'];
215 if ($type == ARTIFACT_EXTRAFIELDTYPE_SELECT ||
216 $type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX ||
217 $type == ARTIFACT_EXTRAFIELDTYPE_RADIO ||
218 $type == ARTIFACT_EXTRAFIELDTYPE_STATUS ||
219 $type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT) {
220 $efarr[$i]['field_type'] = ARTIFACT_EXTRAFIELDTYPE_MULTISELECT;
221 } elseif ($type == ARTIFACT_EXTRAFIELDTYPE_USER ||
222 $type == ARTIFACT_EXTRAFIELDTYPE_MULTIUSER) {
223 $efarr[$i]['field_type'] = ARTIFACT_EXTRAFIELDTYPE_MULTIUSER;
224 } elseif ($type == ARTIFACT_EXTRAFIELDTYPE_RELEASE ||
225 $type == ARTIFACT_EXTRAFIELDTYPE_MULTIRELEASE) {
226 $efarr[$i]['field_type'] = ARTIFACT_EXTRAFIELDTYPE_MULTIRELEASE;
227 } elseif ($type == ARTIFACT_EXTRAFIELDTYPE_DATETIME ||
228 $type == ARTIFACT_EXTRAFIELDTYPE_DATE) {
229 $efarr[$i]['field_type'] = ARTIFACT_EXTRAFIELDTYPE_DATERANGE;
230 } elseif ($type == ARTIFACT_EXTRAFIELDTYPE_EFFORT) {
231 $efarr[$i]['field_type'] = ARTIFACT_EXTRAFIELDTYPE_EFFORTRANGE;
233 $efarr[$i]['field_type'] = ARTIFACT_EXTRAFIELDTYPE_TEXT;
238 // 'DISPLAY' mode is for rendering in 'read-only' mode (for detail view).
239 if ($mode === 'DISPLAY') {
240 $keys=array_keys($efarr);
241 for ($k=0; $k<count($keys); $k++) {
245 if (!isset($selected[$efarr[$i]['extra_field_id']]))
246 $selected[$efarr[$i]['extra_field_id']] = '';
248 $value = $selected[$efarr[$i]['extra_field_id']];
249 $type = $efarr[$i]['field_type'];
251 if ($type == ARTIFACT_EXTRAFIELDTYPE_SELECT ||
252 $type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX ||
253 $type == ARTIFACT_EXTRAFIELDTYPE_RADIO ||
254 $type == ARTIFACT_EXTRAFIELDTYPE_STATUS ||
255 $type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT) {
257 $value = $efarr[$i]['show100label'];
259 $arr = $this->getExtraFieldElements($efarr[$i]['extra_field_id']);
261 // Convert the values (ids) to names in the ids order.
263 for ($j=0; $j<count($arr); $j++) {
264 if (is_array($value) && in_array($arr[$j]['element_id'], $value)) {
265 $new[]= $arr[$j]['element_name'];
266 } elseif ($arr[$j]['element_id'] === $value) {
267 $new[] = $arr[$j]['element_name'];
270 $value = join('<br />', $new);
272 } elseif ($type == ARTIFACT_EXTRAFIELDTYPE_TEXT ||
273 $type == ARTIFACT_EXTRAFIELDTYPE_TEXTAREA) {
274 $value = preg_replace('/((http|https|ftp):\/\/\S+)/',
275 "<a href=\"\\1\" target=\"_blank\">\\1</a>", $value);
276 } elseif ($type == ARTIFACT_EXTRAFIELDTYPE_RELATION) {
277 // Convert artifact id to links.
278 $value = preg_replace_callback('/\b(\d+)\b/', create_function('$matches', 'return _artifactid2url($matches[1], \'title\');'), $value);
279 } elseif ($type == ARTIFACT_EXTRAFIELDTYPE_DATETIME && $value!='') {
280 $value = date('Y-m-d H:i', $value);
281 } elseif ($type == ARTIFACT_EXTRAFIELDTYPE_EFFORT) {
282 if (!isset($effortUnitSet)) {
283 $effortUnitSet = New EffortUnitSet($this, $this->getEffortUnitSet());
284 $effortUnitFactory = New EffortUnitFactory($effortUnitSet);
286 $value = $effortUnitFactory->encodedToString($value);
288 $template = str_replace('{$PostName:'.$efarr[$i]['field_name'].'}', $post_name, $template);
289 $template = str_replace('{$'.$efarr[$i]['field_name'].'}', $value, $template);
294 if ($mode == 'UPDATE' || $mode == 'NEW') {
295 if ($mode == 'NEW') {
296 $efInFormula = $this->getExtraFieldsInFormula($types, false, false);
297 $efWithFormula = $this->getExtraFieldsWithFormula($types, false, false);
299 $efInFormula = $this->getExtraFieldsInFormula($types);
300 $efWithFormula = $this->getExtraFieldsWithFormula($types);
304 $keys = array_keys($efarr);
305 for ($k = 0; $k < count($keys); $k++) {
310 if (!empty($efarr[$i]['description'])) {
311 $attrs['title'] = $efarr[$i]['description'];
313 if ($efarr[$i]['is_required'] == 1 && $mode != 'QUERY') {
314 $attrs['required'] = 'required';
317 if ($mode == 'UPDATE' || $mode == 'NEW') {
318 if (in_array($efarr[$i]['extra_field_id'], $efInFormula)) {
319 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'in-formula';
321 if (in_array($efarr[$i]['extra_field_id'], $efWithFormula)) {
322 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'with-formula readonly';
323 $attrs['readonly'] = 'readonly';
327 if (!isset($selected[$efarr[$i]['extra_field_id']]))
328 $selected[$efarr[$i]['extra_field_id']] = '';
330 if ($status_show_100) {
331 $efarr[$i]['show100'] = $status_show_100;
334 if ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_SELECT ||
335 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX ||
336 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_RADIO ||
337 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT) {
339 if (!is_null($efarr[$i]['parent']) && !empty($efarr[$i]['parent']) && $efarr[$i]['parent']!='100') {
340 $aefParentId = $efarr[$i]['parent'];
341 $selectedElmnts = (isset($selected[$aefParentId]) ? $selected[$aefParentId] : '');
342 $aef = new ArtifactExtraField($this, $efarr[$i]['extra_field_id']);
343 $allowed = $aef->getAllowedValues($selectedElmnts);
347 if ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_SELECT) {
348 $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);
350 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) {
352 $str = $this->renderCheckbox($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['show100'], $efarr[$i]['show100label'], $allowed, $attrs);
354 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_RADIO) {
356 $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);
358 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_TEXT ||
359 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_INTEGER) {
361 if (!empty($efarr[$i]['pattern'])) {
362 $attrs['pattern'] = $efarr[$i]['pattern'];
364 if ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_TEXT) {
365 $str = $this->renderTextField($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['attribute1'], $efarr[$i]['attribute2'], $attrs);
367 $str = $this->renderIntegerField($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['attribute1'], $efarr[$i]['attribute2'], $attrs);
369 if ($mode == 'QUERY') {
370 $post_name = ' <i>'._('(%% for wildcards)').'</i> ';
373 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_TEXTAREA) {
375 $str = $this->renderTextArea($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['attribute1'], $efarr[$i]['attribute2'], $attrs);
376 if ($mode == 'QUERY') {
377 $post_name = ' <i>'._('(%% for wildcards)').'</i>';
380 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT) {
382 $str = $this->renderMultiSelectBox($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['show100'], $efarr[$i]['show100label'], $allowed, $attrs);
384 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
386 // Get the allowed values from the workflow.
387 $atw = new ArtifactWorkflow($this, $efarr[$i]['extra_field_id']);
389 // Special treatment for the initial step (Submit).
390 // In this case, the initial value is the first value.
391 if (isset($selected[$efarr[$i]['extra_field_id']]) && $selected[$efarr[$i]['extra_field_id']]) {
392 $selected_node = $selected[$efarr[$i]['extra_field_id']];
394 $selected_node = 100;
397 $allowed = $atw->getNextNodes($selected_node);
398 $allowed[] = $selected_node;
399 $str = $this->renderSelect($efarr[$i]['extra_field_id'], $selected_node, $status_show_100, $text_100, $show_any, $text_any, $allowed, $attrs);
401 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_RELATION) {
403 $str = $this->renderRelationField($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['attribute1'], $efarr[$i]['attribute2'], $attrs);
404 if ($mode == 'UPDATE' || $mode == 'NEW') {
405 $post_name = html_image('ic/forum_edit.png', 37, 15 ,array('title'=>"Click to edit", 'alt'=>"Click to edit", 'onclick'=>"switch2edit(this, 'show$i', 'edit$i')"));
407 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_DATETIME) {
408 $str = $this->renderDatetime($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $attrs);
409 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_USER) {
410 $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);
411 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_MULTIUSER) {
412 $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);
413 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_RELEASE) {
414 $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);
415 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_MULTIRELEASE) {
416 $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);
417 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_DATERANGE) {
418 if ($mode == 'QUERY') {
419 $post_name = ' <i>'._('(YYYY-MM-DD YYYY-MM-DD Format)').'</i>';
421 $str = $this->renderDateRange($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $attrs);
422 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_EFFORT) {
423 $str = $this->renderEffort($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['attribute1'], $efarr[$i]['attribute2'], $attrs);
424 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_EFFORTRANGE) {
425 $str = $this->renderEffortRange($efarr[$i]['extra_field_id'], $selected[$efarr[$i]['extra_field_id']], $efarr[$i]['attribute1'], $efarr[$i]['attribute2'], $attrs);
427 $template = str_replace('{$PostName:'.$efarr[$i]['field_name'].'}', $post_name, $template);
428 $template = str_replace('{$'.$efarr[$i]['field_name'].'}', $str, $template);
430 if($template != NULL){
431 if ($mode == 'UPDATE' || $mode == 'NEW' || $mode == 'QUERY') {
432 echo $this->javascript();
438 function renderRelatedTasks($group, $ah, $formid = null) {
440 if (!$group->usesPM()) {
446 $taskcount = db_numrows($ah->getRelatedTasks());
447 db_result_reset($ah->getRelatedTasks());
449 if (forge_check_perm('tracker_admin', $ah->ArtifactType->Group->getID())) {
454 $totalPercentage = 0;
455 if ($taskcount > 0) {
456 $title_arr = array();
457 $title_arr[] = _('Task Id and Summary');
458 $title_arr[] = _('Progress');
459 $title_arr[] = _('Start Date');
460 $title_arr[] = _('End Date');
461 $title_arr[] = _('Status');
462 (($is_admin) ? $title_arr[]=_('Remove Relation') : '');
463 $return .= $HTML->listTableTop($title_arr);
465 for ($i = 0; $i < $taskcount; $i++) {
466 $taskinfo = db_fetch_array_by_row($ah->relatedtasks, $i);
467 $totalPercentage += $taskinfo['percent_complete'];
468 $taskid = $taskinfo['project_task_id'];
469 $projectid = $taskinfo['group_project_id'];
470 $groupid = $taskinfo['group_id'];
471 if (forge_check_perm('pm', $projectid, 'read')) {
472 $summary = util_unconvert_htmlspecialchars($taskinfo['summary']);
473 $startdate = date(_('Y-m-d H:i'), $taskinfo['start_date']);
474 $enddate = date(_('Y-m-d H:i'), $taskinfo['end_date']);
475 $status = $taskinfo['status_name'];
477 <td>'.util_make_link('/pm/t_follow.php/'.$taskid, '[T'.$taskid.'] '.$summary).'</td>
478 <td><div class="percentbar" style="width: 100px;">
479 <div style="width:'.round($taskinfo['percent_complete']).'px;"></div></div></td>
480 <td>'.$startdate.'</td>
481 <td>'.$enddate.'</td>
482 <td>'.$status.' ('.$taskinfo['percent_complete'].'%)</td>'.
483 (($is_admin) ? '<td><input type="checkbox" '.(($formid) ? 'form="'.$formid.'"' : '').' name="remlink[]" value="'.$taskid.'" /></td>' : '').
487 $return .= $HTML->listTableBottom();
489 $return .= "\n<hr /><p style=\"text-align:right;\">";
490 $return .= _('Average completion rate')._(': ').(int)($totalPercentage/$taskcount).'%';
493 $return .= $HTML->information(_('No related tasks'));
498 function renderFiles($group_id, $ah) {
500 $file_list =& $ah->getFiles();
501 $count=count($file_list);
505 $return .= '<strong>'._("Attachments")._(':').'</strong>'.'<br/>';
507 $title_arr[] = _('Size');
508 $title_arr[] = _('Name');
509 $title_arr[] = _('Date');
510 $title_arr[] = _('By');
511 $title_arr[] = _('Download');
512 if (forge_check_perm('tracker', $this->getID(), 'tech')) {
515 $return .= $HTML->listTableTop($title_arr);
517 foreach ($file_list as $file) {
519 $return .= '<td>'.human_readable_bytes($file->getSize()).'</td>';
520 $return .= '<td>'.htmlspecialchars($file->getName()).'</td>';
521 $return .= '<td>'.date(_('Y-m-d H:i'), $file->getDate()).'</td>';
522 $return .= '<td>'.util_display_user($file->getSubmittedUnixName(), $file->getSubmittedBy(), $file->getSubmittedRealName()).'</td>';
523 $return .= '<td>'.util_make_link('/tracker/download.php/'.$group_id.'/'. $this->getID().'/'. $ah->getID() .'/'.$file->getID().'/'.$file->getName(), htmlspecialchars($file->getName())).'</td>';
524 if (forge_check_perm('tracker', $this->getID(), 'tech')) {
525 $return .= '<td><input type="checkbox" name="delete_file[]" value="'. $file->getID() .'">'._('Delete').'</td>';
530 $return .= $HTML->listTableBottom();
532 $return .= $HTML->information(_('No attached documents'));
540 * @param array $types
541 * @param string $mode
542 * @return string HTML template.
544 function getRenderHTML($types=array(), $mode='') {
545 // Use template only for the browse (not for query or mass update)
546 if (($mode === 'DISPLAY' || $mode === 'DETAIL' || $mode === 'UPDATE' || $mode == 'NEW')
547 && $this->data_array['custom_renderer']) {
548 return preg_replace('/<!--(\S+.*?)-->/','{$\\1}', $this->data_array['custom_renderer']);
550 return $this->generateRenderHTML($types, $mode);
557 * @param array $types
558 * @param string $mode Display mode (QUERY OR DISPLAY OR NEW)
559 * @return string HTML template.
561 function generateRenderHTML($types=array(), $mode) {
562 if ($mode == 'NEW') {
563 $efarr = $this->getExtraFields($types, false, false);
565 $efarr = $this->getExtraFields($types);
567 //each two columns, we'll reset this and start a new row
570 <!-- Start Extra Fields Rendering -->
574 $keys=array_keys($efarr);
576 if ($count == 0) return '';
578 for ($k=0; $k<$count; $k++) {
581 // Do not show the required star in query mode (creating/updating a query).
582 $is_required = ($mode == 'QUERY' || $mode == 'DISPLAY') ? 0 : $efarr[$i]['is_required'];
583 if ($mode == 'QUERY' && ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_DATETIME || $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_DATE)) {
584 $name = sprintf(_('%s range'), $efarr[$i]['field_name']).($is_required ? utils_requiredField() : '')._(': ');
586 $name = $efarr[$i]['field_name'].($is_required ? utils_requiredField() : '')._(': ');
588 $name = '<strong>'.$name.'</strong>';
590 if ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_SELECT ||
591 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX ||
592 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_RADIO ||
593 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT ||
594 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_STATUS ||
595 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_USER ||
596 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_RELEASE ) {
599 <td class="halfwidth top">'.$name.'<br />{$'.$efarr[$i]['field_name'].'}</td>';
601 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_TEXT ||
602 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_INTEGER ||
603 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_RELATION ||
604 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_DATETIME ||
605 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_EFFORT) {
607 //text fields might be really wide, so need a row to themselves.
608 if (($col_count == 1) && ($efarr[$i]['attribute1'] > 30)) {
618 <td style="width:'.(50*$colspan).'%" colspan="'.$colspan.'" class="top">'.$name.'{$PostName:'.$efarr[$i]['field_name'].'}<br />{$'.$efarr[$i]['field_name'].'}</td>';
620 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_TEXTAREA) {
622 //text areas might be really wide, so need a row to themselves.
623 if (($col_count == 1) && ($efarr[$i]['attribute2'] > 30)) {
633 <td style="width:'.(50*$colspan).'%" colspan="'.$colspan.'" class="top">'.$name.'{$PostName:'.$efarr[$i]['field_name'].'}<br />{$'.$efarr[$i]['field_name'].'}</td>';
638 //we've done two columns - if there are more to do, start a new row
639 if (($col_count == 2) && ($k != $count-1)) {
646 if ($col_count == 1) {
652 <!-- End Extra Fields Rendering -->';
657 * renderSelect - this function builds pop up box with choices.
659 * @param int $extra_field_id The ID of this field.
660 * @param string $checked The item that should be checked
661 * @param bool|string $show_100 Whether to show the '100 row'
662 * @param string $text_100 What to call the '100 row'
663 * @param bool $show_any
664 * @param string $text_any
665 * @param bool $allowed
666 * @param array $attrs
667 * @return string HTML code for the box and choices
669 function renderSelect($extra_field_id, $checked='xzxz', $show_100=false, $text_100='none', $show_any=false, $text_any='Any', $allowed=false, $attrs = array()) {
670 if ($text_100 == 'none'){
673 $arr = $this->getExtraFieldElements($extra_field_id);
674 $aef = new ArtifactExtraField($this, $extra_field_id);
675 $aefChildren = $aef->getChildren();
676 if (!empty($aefChildren)) {
677 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'with-depcy';
679 if ($aef->isAutoAssign()) {
680 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'autoassign';
684 $opt_attrs = array();
685 $attrs_100 = array();
687 for ($i=0; $i<count($arr); $i++) {
688 $vals[$i]=$arr[$i]['element_id'];
689 $texts[$i]=$arr[$i]['element_name'];
690 $opt_attrs[$i]=array();
691 $aefe = new ArtifactExtraFieldElement($aef, $arr[$i]['element_id']);
692 if (!empty($aefChildren)) {
693 $cElmntArr = $aefe->getChildrenElements();
694 if (!empty($cElmntArr))
697 foreach ($cElmntArr as $key=>$cElmnt) {
698 $childField = new ArtifactExtraField($this, $key);
699 $dependency .= (empty($dependency) ? '':', ').'{"field":'.$key.', "elmnt": ['.implode(', ', $cElmnt).']}';
701 $dependency = '['.$dependency.']';
702 $opt_attrs[$i]= array('data-dependency'=>$dependency);
705 if ($aef->isAutoAssign()) {
706 $autoAssignTo = UserManager::instance()->getUserById($aefe->getAutoAssignto());
707 $opt_attrs[$i]=array_merge(isset($opt_attrs[$i]) ? $opt_attrs[$i] : array(), array('data-autoassignto'=>'{"id":'.$aefe->getAutoAssignto().', "name":"'.$autoAssignTo->getRealName().' ('.$autoAssignTo->getUnixName().')"}'));
711 if ($show_100 && !empty($aefChildren)) {
712 $attrs_100 = array('data-dependency'=>'{"fields": ['.implode(', ', $aefChildren).']}');
714 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);
718 * renderUserField - this function builds pop up box with users.
720 * @param int $extra_field_id The ID of this field.
721 * @param string $checked The item that should be checked
722 * @param bool|string $show_100 Whether to show the '100 row'
723 * @param string $text_100 What to call the '100 row'
724 * @param bool $show_any
725 * @param string $text_any
726 * @param bool $allowed
727 * @param array $attrs
728 * @return string HTML code for the box and choices
730 function renderUserField($extra_field_id, $checked='xzxz', $show_100=false, $text_100='none', $show_any=false, $text_any='Any', $allowed=false, $attrs = array()) {
731 if ($text_100 == 'none' || $text_100 == 'nobody'){
732 $text_100=_('Nobody');
734 $arr = $this->getExtraFieldElements($extra_field_id);
735 $selectedRolesId = array();
736 for ($i=0; $i<count($arr); $i++) {
737 $selectedRolesId[$i]=$arr[$i]['element_name'];
739 $roles = $this->getGroup()->getRoles();
740 $userArray = array();
741 foreach ($roles as $role) {
742 if (in_array($role->getID(), $selectedRolesId)) {
743 foreach ($role->getUsers() as $user) {
744 $userArray[$user->getID()] = $user->getRealName().(($user->getStatus()=='S') ? ' '._('[SUSPENDED]') : '');
748 if (is_integer($checked) && !isset($userArray[$checked])) {
749 $checkedUser = user_get_object($checked);
750 $userArray[$checkedUser->getID()] = $checkedUser->getRealName().' '._('[DELETED]');
752 asort($userArray,SORT_FLAG_CASE | SORT_STRING);
753 $keys = array_keys($userArray);
754 $vals = array_values($userArray);
755 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);
759 * renderMultiUserField - this function builds pop up box with users.
761 * @param int $extra_field_id The ID of this field.
762 * @param string $checked The item that should be checked
763 * @param bool|string $show_100 Whether to show the '100 row'
764 * @param string $text_100 What to call the '100 row'
765 * @param bool $show_any
766 * @param string $text_any
767 * @param bool $allowed
768 * @param array $attrs
769 * @return string HTML code for the box and choices
771 function renderMultiUserField($extra_field_id, $checked='xzxz', $show_100=false, $text_100='none', $show_any=false, $text_any='Any', $allowed=false, $attrs = array()) {
772 if ($text_100 == 'none' || $text_100 == 'nobody'){
773 $text_100=_('Nobody');
779 if (!is_array($checked)) {
780 $checked = explode(',', $checked);
783 $arr = $this->getExtraFieldElements($extra_field_id);
784 $selectedRolesId = array();
785 for ($i=0; $i<count($arr); $i++) {
786 $selectedRolesId[$i]=$arr[$i]['element_name'];
788 $roles = $this->getGroup()->getRoles();
789 $userArray = array();
790 foreach ($roles as $role) {
791 if (in_array($role->getID(), $selectedRolesId)) {
792 foreach ($role->getUsers() as $user) {
793 $userArray[$user->getID()] = $user->getRealName().(($user->getStatus()=='S') ? ' '._('[SUSPENDED]') : '');
797 if (is_integer($checked) && !isset($userArray[$checked])) {
798 $checkedUser = user_get_object($checked);
799 $userArray[$checkedUser->getID()] = $checkedUser->getRealName().' '._('[DELETED]');
801 asort($userArray,SORT_FLAG_CASE | SORT_STRING);
802 $size = min(count($userArray)+1, 15);
803 $keys = array_keys($userArray);
804 $vals = array_values($userArray);
806 return html_build_multiple_select_box_from_arrays($keys, $vals,'extra_fields['.$extra_field_id.'][]', $checked, $size, $show_100, $text_100, $allowed, $attrs);
810 * renderReleaseField - this function builds 2 pop up boxes with packages & releases.
812 * @param int $extra_field_id The ID of this field.
813 * @param string $checked The item that should be checked
814 * @param bool|string $show_100 Whether to show the '100 row'
815 * @param string $text_100 What to call the '100 row'
816 * @param bool $show_any
817 * @param string $text_any
818 * @param bool $allowed
819 * @param array $attrs
820 * @return string HTML code for the box and choices
822 function renderReleaseField($extra_field_id, $checked = 'xzxz', $show_100 = false, $text_100 = 'none', $show_any = false, $text_any = 'Any', $allowed = false, $attrs = array()) {
823 if ($text_100 == 'none'){
827 $releasesArray = array();
828 $releasesAttrs = array();
831 $arr = $this->getExtraFieldElements($extra_field_id);
832 $selectedPackagesId = array();
833 for ($i=0; $i<count($arr); $i++) {
834 $selectedPackagesId[$i]=$arr[$i]['element_name'];
837 $fpFactory = new FRSPackageFactory($this->getGroup());
838 $packages = $fpFactory->getFRSs(true);
839 uasort($packages, 'compareObjectName');
840 foreach ($packages as $package) {
841 $releases = $package->getReleases(false);
842 uasort($releases, 'compareObjectName');
843 foreach ($releases as $release) {
844 $optGroup[] = $package->getName();
845 $releasesArray[$release->getID()] = $release->getName();
849 $keys = array_keys($releasesArray);
850 $vals = array_values($releasesArray);
851 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);
855 * renderMultiReleaseField - this function builds 2 pop up boxes with packages & releases.
857 * @param int $extra_field_id The ID of this field.
858 * @param string $checked The item that should be checked
859 * @param bool|string $show_100 Whether to show the '100 row'
860 * @param string $text_100 What to call the '100 row'
861 * @param bool $show_any
862 * @param string $text_any
863 * @param bool $allowed
864 * @param array $attrs
865 * @return string HTML code for the box and choices
867 function renderMultiReleaseField($extra_field_id, $checked='xzxz', $show_100=false, $text_100='none', $show_any=false, $text_any='Any', $allowed=false, $attrs=array()) {
868 if ($text_100 == 'none'){
874 if (!is_array($checked)) {
875 $checked = explode(',', $checked);
878 $releasesArray = array();
879 $releasesAttrs = array();
882 $arr = $this->getExtraFieldElements($extra_field_id);
883 $selectedPackagesId = array();
884 for ($i=0; $i<count($arr); $i++) {
885 $selectedPackagesId[$i]=$arr[$i]['element_name'];
888 $packages = get_frs_packages($this->getGroup());
889 uasort($packages, 'compareObjectName');
890 foreach ($packages as $package) {
891 if (in_array($package->getID(), $selectedPackagesId)) {
892 $releases = $package->getReleases();
893 uasort($releases, 'compareObjectName');
894 foreach ($releases as $release) {
895 $optGroup[] = $package->getName();
896 $releasesArray[$release->getID()] = $release->getName();
900 $size = min(count($releasesArray) + count($optGroup) + 1, 15);
901 $keys = array_keys($releasesArray);
902 $vals = array_values($releasesArray);
903 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);
907 * renderRadio - this function builds radio buttons.
909 * @param int $extra_field_id The $int ID of this field.
910 * @param string $checked The $string item that should be checked
911 * @param bool $show_100 Whether $string to show the '100 row'
912 * @param string $text_100 What $string to call the '100 row'
913 * @param bool $show_any
914 * @param string $text_any
915 * @param array $attrs Array of other attributes
916 * @return string HTML code using radio buttons
918 function renderRadio($extra_field_id, $checked='xzxz', $show_100=false, $text_100='none', $show_any=false, $text_any='Any', $allowed = false, $attrs = array()) {
920 $arr = $this->getExtraFieldElements($extra_field_id);
921 $aef = new ArtifactExtraField($this, $extra_field_id);
922 $aefChildren = $aef->getChildren();
923 if (!empty($aefChildren)) {
924 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'with-depcy';
929 $radios_attrs = array();
930 $attrs_100 = array();
932 for ($i=0; $i<count($arr); $i++) {
933 $vals[$i]=$arr[$i]['element_id'];
934 $texts[$i]=$arr[$i]['element_name'];
935 $aefe = new ArtifactExtraFieldElement($aef, $arr[$i]['element_id']);
937 if (!empty($aefChildren)) {
938 $cElmntArr = $aefe->getChildrenElements();
939 if (!empty($cElmntArr))
941 foreach ($cElmntArr as $key=>$cElmnt) {
942 $childField = new ArtifactExtraField($this, $key);
943 $dependency .= (empty($dependency) ? '':', ').'{"field":'.$key.', "elmnt": ['.implode(', ', $cElmnt).']}';
945 $dependency = '['.$dependency.']';
946 $radios_attrs[$i]['data-dependency']=$dependency;
949 if ($aef->isAutoAssign()) {
950 $autoAssignTo = UserManager::instance()->getUserById($aefe->getAutoAssignto());
951 $radios_attrs[$i]=array_merge(isset($radios_attrs[$i]) ? $radios_attrs[$i] : array(), array('data-autoassignto'=>'{"id":'.$aefe->getAutoAssignto().', "name":"'.$autoAssignTo->getRealName().' ('.$autoAssignTo->getUnixName().')"}'));
954 if ($show_100 && !empty($aefChildren)) {
955 $attrs_100 = array('data-dependency'=>'{"fields": ['.implode(', ', $aefChildren).']}');
957 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);
961 * renderCheckbox - this function builds checkboxes.
963 * @param int $extra_field_id The ID of this field.
964 * @param array $checked The items that should be checked
965 * @param bool|string $show_100 Whether to show the '100 row'
966 * @param string $text_100 What to call the '100 row'
967 * @param array $attrs Array of other attributes
968 * @return string radio buttons
970 function renderCheckbox($extra_field_id, $checked=array(), $show_100=false, $text_100='none', $allowed=false, $attrs = array()) {
971 if ($text_100 == 'none'){
975 if (!$checked || !is_array($checked)) {
978 if (!empty($attrs['title'])) {
979 $attrs['title'] = util_html_secure($attrs['title']);
982 $arr = $this->getExtraFieldElements($extra_field_id);
983 $aef = new ArtifactExtraField($this, $extra_field_id);
984 $aefChildren = $aef->getChildren();
985 if (!empty($aefChildren)) {
986 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'with-depcy';
991 $chk_attrs = array();
992 $attrs_100 = array();
994 for ($i=0; $i<count($arr); $i++) {
995 $aefe = new ArtifactExtraFieldElement($aef, $arr[$i]['element_id']);
996 $vals[$i] = $arr[$i]['element_id'];
997 $texts[$i] = $arr[$i]['element_name'];
998 $chk_attrs[$i] = array();
1000 if (!empty($aefChildren)) {
1001 $cElmntArr = $aefe->getChildrenElements();
1002 if (!empty($cElmntArr))
1004 foreach ($cElmntArr as $key=>$cElmnt) {
1005 $childField = new ArtifactExtraField($this, $key);
1006 $dependency .= (empty($dependency) ? '':', ').'{"field":'.$key.', "elmnt": ['.implode(', ', $cElmnt).']}';
1008 $dependency = '['.$dependency.']';
1009 $chk_attrs[$i]['data-dependency']=$dependency;
1013 if ($show_100 && !empty($aefChildren)) {
1014 $attrs_100['data-dependency'] ='{"fields": ['.implode(', ', $aefChildren).']}';
1016 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);
1020 * renderMultiSelectBox - this function builds checkboxes.
1022 * @param int $extra_field_id The ID of this field.
1023 * @param array $checked The items that should be checked
1024 * @param bool|string $show_100 Whether to show the '100 row'
1025 * @param string $text_100 What to call the '100 row'
1026 * @param array $attrs Array of other attributes
1027 * @return string radio multiselectbox
1029 function renderMultiSelectBox($extra_field_id, $checked=array(), $show_100=false, $text_100='none', $allowed=false, $attrs=array()) {
1033 if (!is_array($checked)) {
1034 $checked = explode(',', $checked);
1036 $arr = $this->getExtraFieldElements($extra_field_id);
1037 $aef = new ArtifactExtraField($this, $extra_field_id);
1038 $aefChildren = $aef->getChildren();
1039 if (!empty($aefChildren)) {
1040 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'with-depcy';
1044 $opt_attrs = array();
1045 $attrs_100 = array();
1047 for ($i=0; $i<count($arr); $i++) {
1048 $vals[$i]=$arr[$i]['element_id'];
1049 $texts[$i]=$arr[$i]['element_name'];
1050 $aefe = new ArtifactExtraFieldElement($aef, $arr[$i]['element_id']);
1051 if (!empty($aefChildren)) {
1052 $cElmntArr = $aefe->getChildrenElements();
1053 if (!empty($cElmntArr))
1056 foreach ($cElmntArr as $key=>$cElmnt) {
1057 $childField = new ArtifactExtraField($this, $key);
1058 $dependency .= (empty($dependency) ? '':', ').'{"field":'.$key.', "elmnt": ['.implode(', ', $cElmnt).']}';
1060 $dependency = '['.$dependency.']';
1061 $opt_attrs[$i]= array('data-dependency'=>$dependency);
1065 $size = min(count($arr)+1, 15);
1066 if ($show_100 && !empty($aefChildren)) {
1067 $attrs_100 = array('data-dependency'=>'{"fields": ['.implode(', ', $aefChildren).']}');
1069 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);
1073 * renderTextField - this function builds a text field.
1075 * @param int $extra_field_id The ID of this field.
1076 * @param string $contents The data for this field.
1077 * @param string $size
1078 * @param string $maxlength
1079 * @param array $attrs Array of other attributes
1080 * @return string HTML code of corresponding input tag.
1082 function renderTextField($extra_field_id, $contents, $size, $maxlength, $attrs = array()) {
1083 return html_e('input', array_merge(array('type'=>'text', 'name'=>'extra_fields['.$extra_field_id.']', 'value'=>$contents, 'size'=>$size, 'maxlength'=>$maxlength), $attrs));
1087 * renderIntegerField - this function builds a text field.
1089 * @param int $extra_field_id The ID of this field.
1090 * @param string $contents The data for this field.
1091 * @param string $size
1092 * @param string $maxlength
1093 * @param array $attrs Array of other attributes
1094 * @return string HTML code of corresponding input tag.
1096 function renderIntegerField($extra_field_id, $contents, $size, $maxlength, $attrs = array()) {
1097 $intAttrs = array('type'=>'number', 'name'=>'extra_fields['.$extra_field_id.']', 'value'=>$contents, 'size'=>$size, 'maxlength'=>$maxlength, 'min'=>0);
1098 $newattrs = array_merge($intAttrs, $attrs);
1099 return html_e('input', $newattrs);
1103 * renderRelationField - this function builds a relation field.
1105 * @param int $extra_field_id The ID of this field.
1106 * @param string $contents The data for this field.
1107 * @param string $size
1108 * @param string $maxlength
1109 * @param array $attrs Array of other attributes
1110 * @return string text area and data.
1112 function renderRelationField($extra_field_id, $contents, $size, $maxlength, $attrs = array()) {
1113 $arr = $this->getExtraFieldElements($extra_field_id);
1114 for ($i=0; $i<count($arr); $i++) {
1115 $keys[$i]=$arr[$i]['element_id'];
1116 $vals[$i]=$arr[$i]['element_name'];
1118 // Convert artifact id to links.
1119 $html_contents = preg_replace_callback('/\b(\d+)\b/', create_function('$matches', 'return _artifactid2url($matches[1], \'title\');'), $contents);
1120 $edit_contents = $this->renderTextField ($extra_field_id, $contents, $size, $maxlength);
1121 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)
1122 .html_e('div',array_merge(array('id'=>'show'.$extra_field_id, 'style'=>'display: block'), $attrs), $html_contents);
1126 * renderTextArea - this function builds a text area.
1128 * @param int $extra_field_id The ID of this field.
1129 * @param string $contents The data for this field.
1130 * @param string $rows
1131 * @param string $cols
1132 * @param array $attrs Array of other attributes
1133 * @return string text area and data.
1135 function renderTextArea($extra_field_id, $contents, $rows, $cols, $attrs = array()) {
1136 return html_e('textarea', array_merge(array('name'=>'extra_fields['.$extra_field_id.']', 'rows'=>$rows, 'cols'=>$cols), $attrs), $contents, false);
1140 * renderDatetime - this function builds a Datetime field.
1142 * @param int $extra_field_id The ID of this field.
1143 * @param string $datetime datetime for this field.
1144 * @param array $attrs Array of other attributes
1145 * @return string datetime.
1147 function renderDatetime($extra_field_id, $datetime, $attrs = array()) {
1148 if (!$datetime=='') {
1149 $datetime_format = _('Y-m-d H:i');
1150 $datetime = date($datetime_format, $datetime);
1152 if (isset($attrs['class'])) {
1153 $attrs['class'] = $attrs['class'] . ' datetimepicker';
1155 $attrs['class'] = 'datetimepicker';
1157 return html_e('input', array_merge(array('type'=>'text', 'name'=>'extra_fields['.$extra_field_id.']', 'value'=>$datetime), $attrs));
1160 function renderDateRange($extra_field_id, $dateRange, $attrs = array()) {
1161 // http://html5pattern.com/Dates
1162 // Date with leapyear-check
1163 $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)))';
1164 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));
1168 * renderEffort - this function builds a Effort extra field.
1170 * @param int $extra_field_id The ID of this field.
1171 * @param string $contents The data for this field.
1172 * @param string $size
1173 * @param string $maxlength
1174 * @param array $attrs Array of other attributes
1175 * @return string text area and data.
1177 function renderEffort($extra_field_id, $contents, $size, $maxlength, $attrs = array()) {
1178 $effortUnitSet = New EffortUnitSet($this, $this->getEffortUnitSet());
1179 $effortUnitFactory = New EffortUnitFactory($effortUnitSet);
1180 $units = $effortUnitFactory->getUnits();
1184 foreach ($units as $unit) {
1185 $vals [] = $unit->getID();
1186 $texts [] = $unit->getName();
1187 $opts_attrs []['data-factor'] = $unit->getConversionFactorForBaseUnit();
1189 $valueInUnitBase = $effortUnitFactory->encodedToValueInBaseUnit($contents);
1190 $value = $effortUnitFactory->encodedToValue($contents);
1191 $unitId = $effortUnitFactory->encodedToUnitId($contents);
1192 if (isset($attrs['class'])) {
1193 $attrs['class'] .= ' effort';
1195 $attrs['class'] = 'effort';
1197 $attrs['data-effortid'] = $extra_field_id;
1198 $hiddenAttrs = array();
1199 if (isset($attrs['form'])) {
1200 $hiddenAttrs['form'] = $attrs['form'];
1202 $return = html_e('input', array_merge(array('type'=>'hidden', 'name'=>'extra_fields['.$extra_field_id.']', 'value'=>$valueInUnitBase.'U'.$unitId), $hiddenAttrs));
1203 $return .= html_e('input', array_merge(array('type'=>'number', 'name'=>'value['.$extra_field_id.']', 'value'=>$value, 'size'=>$size, 'maxlength'=>$maxlength, 'min'=>0), $attrs));
1204 $return .= html_build_select_box_from_arrays($vals, $texts, 'unit['.$extra_field_id.']', $unitId, false, '', false, '', false, $attrs, $opts_attrs);
1208 function renderEffortRange($extra_field_id, $contents, $size, $maxlength, $attrs = array()) {
1209 $effortUnitSet = New EffortUnitSet($this, $this->getEffortUnitSet());
1210 $effortUnitFactory = New EffortUnitFactory($effortUnitSet);
1211 $units = $effortUnitFactory->getUnits();
1214 $opts_attrs = array();
1215 foreach ($units as $unit) {
1216 $vals [] = $unit->getID();
1217 $texts [] = $unit->getName();
1218 $opts_attrs []['data-factor'] = $unit->getConversionFactorForBaseUnit();
1220 $contentsFrom = '0U'.$vals[0];
1221 $contentsTo = '0U'.$vals[0];
1222 if (preg_match('/^(\d+U\d+) ?(\d+U\d+)?$/', $contents, $matches)) {
1223 $contentsFrom = $matches[1];
1224 $contentsTo = $matches[2];
1226 $valueInUnitBaseFrom = $effortUnitFactory->encodedToValueInBaseUnit($contentsFrom);
1227 $valueInUnitBaseTo = $effortUnitFactory->encodedToValueInBaseUnit($contentsTo);
1228 $valueFrom = $effortUnitFactory->encodedToValue($contentsFrom);
1229 $valueTo = $effortUnitFactory->encodedToValue($contentsTo);
1230 $unitIdFrom = $effortUnitFactory->encodedToUnitId($contentsFrom);
1231 $unitIdTo = $effortUnitFactory->encodedToUnitId($contentsTo);
1232 $attrs['data-effortid'] = $extra_field_id;
1233 if (isset($attrs['class'])) {
1234 $attrs['class'] .= ' effort-range';
1236 $attrs['class'] = 'effort-range';
1238 $attrsFrom = $attrs;
1240 $hiddenAttrs = array();
1241 if (isset($attrs['form'])) {
1242 $hiddenAttrs['form'] = $attrs['form'];
1244 $return = html_e('input', array_merge(array('type'=>'hidden', 'name'=>'extra_fields['.$extra_field_id.']', 'value'=>$valueInUnitBaseFrom.'U'.$unitIdFrom.' '.$valueInUnitBaseTo.'U'.$unitIdTo), $hiddenAttrs));
1245 $return .= _('Between')._(':').html_e('br');
1246 $return .= html_e('input', array_merge(array('type'=>'number', 'name'=>'value_from['.$extra_field_id.']', 'value'=>$valueFrom, 'size'=>$size, 'maxlength'=>$maxlength, 'min'=>0), $attrsFrom));
1247 $return .= html_build_select_box_from_arrays($vals, $texts, 'unit_from['.$extra_field_id.']', $unitIdFrom, false, '', false, '', false, $attrsFrom, $opts_attrs);
1248 $return .= html_e('br');
1249 $return .= _('and')._(':').html_e('br');
1250 $return .= html_e('input', array_merge(array('type'=>'number', 'name'=>'value_to['.$extra_field_id.']', 'value'=>$valueTo, 'size'=>$size, 'maxlength'=>$maxlength, 'min'=>0), $attrsTo));
1251 $return .= html_build_select_box_from_arrays($vals, $texts, 'unit_to['.$extra_field_id.']', $unitIdTo, false, '', false, '', false, $attrsTo, $opts_attrs);
1255 function technicianBox($name = 'assigned_to[]', $checked = 'xzxz', $show_100 = true, $text_100 = 'none', $extra_id = '-1', $extra_name = '', $multiple = false, $attrs = array()) {
1256 if ($text_100=='none'){
1257 $text_100=_('Nobody');
1259 $engine = RBACEngine::getInstance();
1260 $techs = $engine->getUsersByAllowedAction('tracker', $this->getID(), 'tech') ;
1265 sortUserList($techs);
1267 foreach ($techs as $tech) {
1268 $ids[] = $tech->getID() ;
1269 $names[] = $tech->getRealName() ;
1272 if ($extra_id != '-1') {
1274 $names[]=$extra_name;
1278 if (!is_array($checked)) {
1279 $checked = explode(',', $checked);
1281 $size = min(count($ids)+1, 15);
1282 return html_build_multiple_select_box_from_arrays($ids, $names, $name, $checked, $size, $show_100, $text_100, false, $attrs);
1284 return html_build_select_box_from_arrays($ids, $names, $name, $checked, $show_100, $text_100, false, false, false, $attrs);
1288 function submitterBox($name = 'submitted_by[]', $checked = 'xzxz', $show_100 = true, $text_100 = 'none', $extra_id = '-1', $extra_name = '', $multiple = false, $attrs = array()) {
1289 if ($text_100=='none'){
1290 $text_100=_('Nobody');
1292 $result = $this->getSubmitters();
1293 $ids =& util_result_column_to_array($result,0);
1294 $names =& util_result_column_to_array($result,1);
1295 if ($extra_id != '-1') {
1297 $names[]=$extra_name;
1301 if (!is_array($checked)) {
1302 $checked = explode(',', $checked);
1304 $size = min(count($ids)+1, 15);
1305 return html_build_multiple_select_box_from_arrays($ids, $names, $name, $checked, $size, $show_100, $text_100, false, $attrs);
1307 return html_build_select_box_from_arrays($ids, $names, $name, $checked, $show_100, $text_100, false, $attrs);
1311 function lastModifierBox($name='last_modified_by[]', $checked='xzxz', $show_100=true, $text_100='none', $extra_id='-1', $extra_name='', $multiple=false) {
1312 if ($text_100=='none'){
1313 $text_100=_('Nobody');
1315 $result = $this->getLastModifiers();
1316 $ids =& util_result_column_to_array($result,0);
1317 $names =& util_result_column_to_array($result,1);
1318 if ($extra_id != '-1') {
1320 $names[]=$extra_name;
1324 if (!is_array($checked)) {
1325 $checked = explode(',', $checked);
1327 $size = min(count($ids)+1, 15);
1328 return html_build_multiple_select_box_from_arrays($ids, $names, $name, $checked, $size, $show_100, $text_100);
1330 return html_build_select_box_from_arrays($ids, $names, $name, $checked, $show_100, $text_100);
1334 function cannedResponseBox($name = 'canned_response', $checked = 'xzxz', $attrs = array()) {
1335 if (!isset($attrs['id'])) {
1336 $attrs['id'] = $name;
1338 return html_build_select_box($this->getCannedResponses(), $name, $checked, true, 'none', false, '', false, $attrs);
1342 * statusBox - show the statuses - automatically shows the "custom statuses" if they exist
1344 * @return string HTML code
1346 function statusBox($name = 'status_id', $checked = 'xzxz', $show_100 = false, $text_100 = 'none', $attrs = array()) {
1347 if ($text_100=='none'){
1348 $text_100=_('None');
1350 return html_build_select_box($this->getStatuses(), $name, $checked, $show_100, $text_100, false, '', false, $attrs);
1354 * priorityBox - show the priorities
1356 * @return string HTML code
1358 function priorityBox($name = 'priority', $checked_val = '3', $nochange = false, $attrs = array(), $show_any = false){
1359 return html_build_priority_select_box($name, $checked_val, $nochange, $attrs, $show_any);
1362 function javascript() {
1364 var invalidSelectMsg = '"._("One or more of the selected options is not allowed")."';
1365 var invalidInputMsg = '". _("This choice is not allowed")."';
1366 var groupId =".$this->Group->getID().";
1367 var atId = ".$this->getID().";";
1369 $effortUnitSet = New EffortUnitSet($this, $this->getEffortUnitSet());
1370 if ($effortUnitSet->isAutoconvert()) {
1371 $jseffort = <<<'EOS'
1372 $("select.effort").on('change', function(){
1373 var effortid = $(this).data("effortid");
1374 var value = parseInt(parseInt($("input[name='extra_fields["+effortid+"]']").val())/$("select[name='unit["+effortid+"]'] option:selected").data('factor'));
1375 $("input[name='value["+effortid+"]']").val(value);
1376 $("input[name='extra_fields["+effortid+"]']").val(value*$("select[name='unit["+effortid+"]'] option:selected").data('factor')+'U'+$("select[name='unit["+effortid+"]']").val());
1378 $("input.effort").on('change', function(){
1379 var effortid = $(this).data("effortid");
1380 var value = $("input[name='value["+effortid+"]']").val();
1381 $("input[name='extra_fields["+effortid+"]']").val(value*$("select[name='unit["+effortid+"]'] option:selected").data('factor')+'U'+$("select[name='unit["+effortid+"]']").val());
1383 $(".effort-range").on('change', function(){
1384 var effortid = $(this).data("effortid");
1385 $("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());
1389 $jseffort = <<<'EOS'
1390 $(".effort").on('change', function(){
1391 var effortid = $(this).data("effortid");
1392 $("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());
1394 $(".effort-range").on('change', function(){
1395 var effortid = $(this).data("effortid");
1396 $("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());
1400 $javascript = <<<'EOS'
1401 function showMessage( msg_text, msg_class) {
1402 $("div#maindiv h1").append($("<p>", { "class": msg_class }).html( msg_text )).show();
1404 $.expr[':'].invalid = function(elem, index, match) {
1405 for (let invalid of document.querySelectorAll(':invalid') ) {
1406 if (elem === invalid) { return true; }
1410 $("input[type='radio'].readonly, input[type='checkbox'].readonly").on('click', function(){
1412 }).on('keydown', function(event){
1413 if(event.keyCode !== 9) return false;
1415 $(".in-formula").on('change', function(){
1419 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(),
1422 success: function(answer){
1423 if(answer['message']) {
1424 showMessage(answer['message'], 'error');
1426 fields = answer['fields'];
1427 $.each(fields, function (index, field) {
1428 if (field.error!=null){
1429 showMessage(field.error, 'error');
1431 fieldObj = $("[name^='extra_fields["+field.id+"]']");
1432 if (fieldObj.is("input[type='checkbox']")){
1433 fieldObj.each(function() {
1435 for (var key in field.value) {
1436 if (field.value[key] == $(this).val()) {
1441 if (in_array > -1) {
1442 $(this).prop("checked",true);
1444 $(this).prop("checked",false);
1447 } else if (fieldObj.is("input[type='radio']")){
1448 fieldObj.each(function() {
1450 for (var key in field.value) {
1451 if (field.value[key] == $(this).val()) {
1456 if (in_array > -1) {
1457 $(this).prop("checked",true);
1459 $(this).prop("checked",false);
1462 } else if (fieldObj.is("input")){
1463 fieldObj.val(field.value);
1464 } else if (fieldObj.is("select")){
1465 fieldObj.val(field.value);
1466 } else if (fieldObj.is("textarea")){
1467 fieldObj.val(field.value);
1474 $(".autoassign[name^='extra_fields']").on('change', function(){
1475 if ($(this).prop('tagName') == 'SELECT') {
1476 var elmnts = $(this).children('option:selected');
1478 var elmnts = $(this).siblings('input:checked');
1480 elmnts.each(function(i){
1481 var aat = $(this).data("autoassignto");
1482 $("select#tracker-assigned_to option[value="+aat.id+"]").prop('selected', true);
1483 $("span#tracker-assigned_to").text(aat.name);
1486 $(".with-depcy[name^='extra_fields']").on('change', function(){
1487 if ($(this).prop('tagName') == 'SELECT') {
1488 var elmnts = $(this).children('option:selected');
1490 var elmnts = $(this).siblings('input:checked');
1492 elmnts.each(function(i){
1493 var dep = $(this).data("dependency");
1494 if (this.value!='100') {
1495 $(dep).each(function(j, val) {
1496 $("select[name^='extra_fields["+val.field+"]']:invalid, input[name^='extra_fields["+val.field+"]']:invalid").each(function() {
1497 this.setCustomValidity("");
1498 $(this).off("change.invalid");
1500 $("select[name^='extra_fields["+val.field+"]'] option").each(function(k,opt){
1501 if (this.value!='100') {
1502 if ($.inArray(parseInt(this.value),val.elmnt)>-1) {
1503 $(this).prop('disabled', false).removeClass('option_disabled');
1505 $(this).prop('disabled', true);
1506 $(this).addClass('option_disabled');
1510 $("input[name^='extra_fields["+val.field+"]']").each(function(k,opt){
1511 if (this.value!='100') {
1512 if ($.inArray(parseInt(this.value),val.elmnt)>-1) {
1513 $(this).prop('disabled', false).removeClass($(this).attr('type')+'_disabled');
1515 $(this).prop('disabled', true);
1516 $(this).addClass($(this).attr('type')+'_disabled');
1522 $(dep.fields).each(function(j, val) {
1523 $("select[name^='extra_fields["+val+"]']:invalid, input[name^='extra_fields["+val+"]']:invalid").each(function() {
1524 this.setCustomValidity("");
1526 $("select[name^='extra_fields["+val+"]'] option.option_disabled").each(function() {
1527 $(this).prop('disabled', false).removeClass('option_disabled');
1529 $("input.radio_disable[name^='extra_fields["+val+"]']").each(function() {
1530 $(this).prop('disabled', false).removeClass('radio_disabled');
1532 $("input.checkbox_disabled[name^='extra_fields["+val+"]']").each(function() {
1533 $(this).prop('disabled', false).removeClass('checkbox_disabled');
1538 $("select[name^='extra_fields'] option:selected:disabled").parent().each(function() {
1539 $(this).children('option:selected:disabled').prop('disabled', false);
1540 this.setCustomValidity(invalidSelectMsg);
1541 $(this).on("change.invalid", function() {
1542 $(this).children('option.option_disabled:not(:disabled):not(:selected)').prop('disabled', true);
1543 if (!$(this).children('option.option_disabled:selected').length) {
1544 this.setCustomValidity("");
1545 $(this).off("change.invalid");
1549 $("input[name^='extra_fields']:checked:disabled").each(function() {
1550 $(this).prop('disabled', false);
1551 this.setCustomValidity(invalidInputMsg);
1552 if ($(this).attr('type') == 'radio') {
1553 $(this).siblings('input[type="radio"]').on("change.invalid", function() {
1554 $(this).siblings('input[type="radio"]:invalid').prop('disabled', true).addClass('input_disabled').each(function() {
1555 this.setCustomValidity("");
1557 $(this).siblings('input[type="radio"]').off("change.invalid");
1558 $(this).off("change.invalid");
1561 $(this).on("change.invalid", function() {
1562 $(this).prop('disabled', true);
1563 this.setCustomValidity("");
1564 $(this).off("change.invalid");
1570 return html_e('script', array('type'=>'text/javascript'), '//<![CDATA['."\n".'$(function(){'.$jsvariable."\n".$jseffort."\n".$javascript.'});'."\n".'//]]>');
1577 // c-file-style: "bsd"