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 $return .= $HTML->listTableTop($title_arr);
514 foreach ($file_list as $file) {
516 $return .= '<td>'.human_readable_bytes($file->getSize()).'</td>';
517 $return .= '<td>'.htmlspecialchars($file->getName()).'</td>';
518 $return .= '<td>'.date(_('Y-m-d H:i'), $file->getDate()).'</td>';
519 $return .= '<td>'.util_display_user($file->getSubmittedUnixName(), $file->getSubmittedBy(), $file->getSubmittedRealName()).'</td>';
520 $return .= '<td>'.util_make_link('/tracker/download.php/'.$group_id.'/'. $this->getID().'/'. $ah->getID() .'/'.$file->getID().'/'.$file->getName(), htmlspecialchars($file->getName())).'</td>';
521 if (forge_check_perm('tracker', $this->getID(), 'tech')) {
522 $return .= '<td><input type="checkbox" name="delete_file[]" value="'. $file->getID() .'">'._('Delete').'</td>';
527 $return .= $HTML->listTableBottom();
529 $return .= $HTML->information(_('No attached documents'));
537 * @param array $types
538 * @param string $mode
539 * @return string HTML template.
541 function getRenderHTML($types=array(), $mode='') {
542 // Use template only for the browse (not for query or mass update)
543 if (($mode === 'DISPLAY' || $mode === 'DETAIL' || $mode === 'UPDATE' || $mode == 'NEW')
544 && $this->data_array['custom_renderer']) {
545 return preg_replace('/<!--(\S+.*?)-->/','{$\\1}', $this->data_array['custom_renderer']);
547 return $this->generateRenderHTML($types, $mode);
554 * @param array $types
555 * @param string $mode Display mode (QUERY OR DISPLAY OR NEW)
556 * @return string HTML template.
558 function generateRenderHTML($types=array(), $mode) {
559 if ($mode == 'NEW') {
560 $efarr = $this->getExtraFields($types, false, false);
562 $efarr = $this->getExtraFields($types);
564 //each two columns, we'll reset this and start a new row
567 <!-- Start Extra Fields Rendering -->
571 $keys=array_keys($efarr);
573 if ($count == 0) return '';
575 for ($k=0; $k<$count; $k++) {
578 // Do not show the required star in query mode (creating/updating a query).
579 $is_required = ($mode == 'QUERY' || $mode == 'DISPLAY') ? 0 : $efarr[$i]['is_required'];
580 if ($mode == 'QUERY' && ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_DATETIME || $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_DATE)) {
581 $name = sprintf(_('%s range'), $efarr[$i]['field_name']).($is_required ? utils_requiredField() : '')._(': ');
583 $name = $efarr[$i]['field_name'].($is_required ? utils_requiredField() : '')._(': ');
585 $name = '<strong>'.$name.'</strong>';
587 if ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_SELECT ||
588 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX ||
589 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_RADIO ||
590 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT ||
591 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_STATUS ||
592 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_USER ||
593 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_RELEASE ) {
596 <td class="halfwidth top">'.$name.'<br />{$'.$efarr[$i]['field_name'].'}</td>';
598 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_TEXT ||
599 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_INTEGER ||
600 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_RELATION ||
601 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_DATETIME ||
602 $efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_EFFORT) {
604 //text fields might be really wide, so need a row to themselves.
605 if (($col_count == 1) && ($efarr[$i]['attribute1'] > 30)) {
615 <td style="width:'.(50*$colspan).'%" colspan="'.$colspan.'" class="top">'.$name.'{$PostName:'.$efarr[$i]['field_name'].'}<br />{$'.$efarr[$i]['field_name'].'}</td>';
617 } elseif ($efarr[$i]['field_type'] == ARTIFACT_EXTRAFIELDTYPE_TEXTAREA) {
619 //text areas might be really wide, so need a row to themselves.
620 if (($col_count == 1) && ($efarr[$i]['attribute2'] > 30)) {
630 <td style="width:'.(50*$colspan).'%" colspan="'.$colspan.'" class="top">'.$name.'{$PostName:'.$efarr[$i]['field_name'].'}<br />{$'.$efarr[$i]['field_name'].'}</td>';
635 //we've done two columns - if there are more to do, start a new row
636 if (($col_count == 2) && ($k != $count-1)) {
643 if ($col_count == 1) {
649 <!-- End Extra Fields Rendering -->';
654 * renderSelect - this function builds pop up box with choices.
656 * @param int $extra_field_id The ID of this field.
657 * @param string $checked The item that should be checked
658 * @param bool|string $show_100 Whether to show the '100 row'
659 * @param string $text_100 What to call the '100 row'
660 * @param bool $show_any
661 * @param string $text_any
662 * @param bool $allowed
663 * @param array $attrs
664 * @return string HTML code for the box and choices
666 function renderSelect($extra_field_id, $checked='xzxz', $show_100=false, $text_100='none', $show_any=false, $text_any='Any', $allowed=false, $attrs = array()) {
667 if ($text_100 == 'none'){
670 $arr = $this->getExtraFieldElements($extra_field_id);
671 $aef = new ArtifactExtraField($this, $extra_field_id);
672 $aefChildren = $aef->getChildren();
673 if (!empty($aefChildren)) {
674 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'with-depcy';
676 if ($aef->isAutoAssign()) {
677 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'autoassign';
681 $opt_attrs = array();
682 $attrs_100 = array();
684 for ($i=0; $i<count($arr); $i++) {
685 $vals[$i]=$arr[$i]['element_id'];
686 $texts[$i]=$arr[$i]['element_name'];
687 $opt_attrs[$i]=array();
688 $aefe = new ArtifactExtraFieldElement($aef, $arr[$i]['element_id']);
689 if (!empty($aefChildren)) {
690 $cElmntArr = $aefe->getChildrenElements();
691 if (!empty($cElmntArr))
694 foreach ($cElmntArr as $key=>$cElmnt) {
695 $childField = new ArtifactExtraField($this, $key);
696 $dependency .= (empty($dependency) ? '':', ').'{"field":'.$key.', "elmnt": ['.implode(', ', $cElmnt).']}';
698 $dependency = '['.$dependency.']';
699 $opt_attrs[$i]= array('data-dependency'=>$dependency);
702 if ($aef->isAutoAssign()) {
703 $autoAssignTo = UserManager::instance()->getUserById($aefe->getAutoAssignto());
704 $opt_attrs[$i]=array_merge(isset($opt_attrs[$i]) ? $opt_attrs[$i] : array(), array('data-autoassignto'=>'{"id":'.$aefe->getAutoAssignto().', "name":"'.$autoAssignTo->getRealName().' ('.$autoAssignTo->getUnixName().')"}'));
708 if ($show_100 && !empty($aefChildren)) {
709 $attrs_100 = array('data-dependency'=>'{"fields": ['.implode(', ', $aefChildren).']}');
711 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);
715 * renderUserField - this function builds pop up box with users.
717 * @param int $extra_field_id The ID of this field.
718 * @param string $checked The item that should be checked
719 * @param bool|string $show_100 Whether to show the '100 row'
720 * @param string $text_100 What to call the '100 row'
721 * @param bool $show_any
722 * @param string $text_any
723 * @param bool $allowed
724 * @param array $attrs
725 * @return string HTML code for the box and choices
727 function renderUserField($extra_field_id, $checked='xzxz', $show_100=false, $text_100='none', $show_any=false, $text_any='Any', $allowed=false, $attrs = array()) {
728 if ($text_100 == 'none' || $text_100 == 'nobody'){
729 $text_100=_('Nobody');
731 $arr = $this->getExtraFieldElements($extra_field_id);
732 $selectedRolesId = array();
733 for ($i=0; $i<count($arr); $i++) {
734 $selectedRolesId[$i]=$arr[$i]['element_name'];
736 $roles = $this->getGroup()->getRoles();
737 $userArray = array();
738 foreach ($roles as $role) {
739 if (in_array($role->getID(), $selectedRolesId)) {
740 foreach ($role->getUsers() as $user) {
741 $userArray[$user->getID()] = $user->getRealName().(($user->getStatus()=='S') ? ' '._('[SUSPENDED]') : '');
745 if (is_integer($checked) && !isset($userArray[$checked])) {
746 $checkedUser = user_get_object($checked);
747 $userArray[$checkedUser->getID()] = $checkedUser->getRealName().' '._('[DELETED]');
749 asort($userArray,SORT_FLAG_CASE | SORT_STRING);
750 $keys = array_keys($userArray);
751 $vals = array_values($userArray);
752 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);
756 * renderMultiUserField - this function builds pop up box with users.
758 * @param int $extra_field_id The ID of this field.
759 * @param string $checked The item that should be checked
760 * @param bool|string $show_100 Whether to show the '100 row'
761 * @param string $text_100 What to call the '100 row'
762 * @param bool $show_any
763 * @param string $text_any
764 * @param bool $allowed
765 * @param array $attrs
766 * @return string HTML code for the box and choices
768 function renderMultiUserField($extra_field_id, $checked='xzxz', $show_100=false, $text_100='none', $show_any=false, $text_any='Any', $allowed=false, $attrs = array()) {
769 if ($text_100 == 'none' || $text_100 == 'nobody'){
770 $text_100=_('Nobody');
776 if (!is_array($checked)) {
777 $checked = explode(',', $checked);
780 $arr = $this->getExtraFieldElements($extra_field_id);
781 $selectedRolesId = array();
782 for ($i=0; $i<count($arr); $i++) {
783 $selectedRolesId[$i]=$arr[$i]['element_name'];
785 $roles = $this->getGroup()->getRoles();
786 $userArray = array();
787 foreach ($roles as $role) {
788 if (in_array($role->getID(), $selectedRolesId)) {
789 foreach ($role->getUsers() as $user) {
790 $userArray[$user->getID()] = $user->getRealName().(($user->getStatus()=='S') ? ' '._('[SUSPENDED]') : '');
794 if (is_integer($checked) && !isset($userArray[$checked])) {
795 $checkedUser = user_get_object($checked);
796 $userArray[$checkedUser->getID()] = $checkedUser->getRealName().' '._('[DELETED]');
798 asort($userArray,SORT_FLAG_CASE | SORT_STRING);
799 $size = min(count($userArray)+1, 15);
800 $keys = array_keys($userArray);
801 $vals = array_values($userArray);
803 return html_build_multiple_select_box_from_arrays($keys, $vals,'extra_fields['.$extra_field_id.'][]', $checked, $size, $show_100, $text_100, $allowed, $attrs);
807 * renderReleaseField - this function builds 2 pop up boxes with packages & releases.
809 * @param int $extra_field_id The ID of this field.
810 * @param string $checked The item that should be checked
811 * @param bool|string $show_100 Whether to show the '100 row'
812 * @param string $text_100 What to call the '100 row'
813 * @param bool $show_any
814 * @param string $text_any
815 * @param bool $allowed
816 * @param array $attrs
817 * @return string HTML code for the box and choices
819 function renderReleaseField($extra_field_id, $checked = 'xzxz', $show_100 = false, $text_100 = 'none', $show_any = false, $text_any = 'Any', $allowed = false, $attrs = array()) {
820 if ($text_100 == 'none'){
824 $releasesArray = array();
825 $releasesAttrs = array();
828 $arr = $this->getExtraFieldElements($extra_field_id);
829 $selectedPackagesId = array();
830 for ($i=0; $i<count($arr); $i++) {
831 $selectedPackagesId[$i]=$arr[$i]['element_name'];
834 $fpFactory = new FRSPackageFactory($this->getGroup());
835 $packages = $fpFactory->getFRSs(true);
836 uasort($packages, 'compareObjectName');
837 foreach ($packages as $package) {
838 $releases = $package->getReleases(false);
839 uasort($releases, 'compareObjectName');
840 foreach ($releases as $release) {
841 $optGroup[] = $package->getName();
842 $releasesArray[$release->getID()] = $release->getName();
846 $keys = array_keys($releasesArray);
847 $vals = array_values($releasesArray);
848 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);
852 * renderMultiReleaseField - this function builds 2 pop up boxes with packages & releases.
854 * @param int $extra_field_id The ID of this field.
855 * @param string $checked The item that should be checked
856 * @param bool|string $show_100 Whether to show the '100 row'
857 * @param string $text_100 What to call the '100 row'
858 * @param bool $show_any
859 * @param string $text_any
860 * @param bool $allowed
861 * @param array $attrs
862 * @return string HTML code for the box and choices
864 function renderMultiReleaseField($extra_field_id, $checked='xzxz', $show_100=false, $text_100='none', $show_any=false, $text_any='Any', $allowed=false, $attrs=array()) {
865 if ($text_100 == 'none'){
871 if (!is_array($checked)) {
872 $checked = explode(',', $checked);
875 $releasesArray = array();
876 $releasesAttrs = array();
879 $arr = $this->getExtraFieldElements($extra_field_id);
880 $selectedPackagesId = array();
881 for ($i=0; $i<count($arr); $i++) {
882 $selectedPackagesId[$i]=$arr[$i]['element_name'];
885 $packages = get_frs_packages($this->getGroup());
886 uasort($packages, 'compareObjectName');
887 foreach ($packages as $package) {
888 if (in_array($package->getID(), $selectedPackagesId)) {
889 $releases = $package->getReleases();
890 uasort($releases, 'compareObjectName');
891 foreach ($releases as $release) {
892 $optGroup[] = $package->getName();
893 $releasesArray[$release->getID()] = $release->getName();
897 $size = min(count($releasesArray) + count($optGroup) + 1, 15);
898 $keys = array_keys($releasesArray);
899 $vals = array_values($releasesArray);
900 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);
904 * renderRadio - this function builds radio buttons.
906 * @param int $extra_field_id The $int ID of this field.
907 * @param string $checked The $string item that should be checked
908 * @param bool $show_100 Whether $string to show the '100 row'
909 * @param string $text_100 What $string to call the '100 row'
910 * @param bool $show_any
911 * @param string $text_any
912 * @param array $attrs Array of other attributes
913 * @return string HTML code using radio buttons
915 function renderRadio($extra_field_id, $checked='xzxz', $show_100=false, $text_100='none', $show_any=false, $text_any='Any', $allowed = false, $attrs = array()) {
917 $arr = $this->getExtraFieldElements($extra_field_id);
918 $aef = new ArtifactExtraField($this, $extra_field_id);
919 $aefChildren = $aef->getChildren();
920 if (!empty($aefChildren)) {
921 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'with-depcy';
926 $radios_attrs = array();
927 $attrs_100 = array();
929 for ($i=0; $i<count($arr); $i++) {
930 $vals[$i]=$arr[$i]['element_id'];
931 $texts[$i]=$arr[$i]['element_name'];
932 $aefe = new ArtifactExtraFieldElement($aef, $arr[$i]['element_id']);
934 if (!empty($aefChildren)) {
935 $cElmntArr = $aefe->getChildrenElements();
936 if (!empty($cElmntArr))
938 foreach ($cElmntArr as $key=>$cElmnt) {
939 $childField = new ArtifactExtraField($this, $key);
940 $dependency .= (empty($dependency) ? '':', ').'{"field":'.$key.', "elmnt": ['.implode(', ', $cElmnt).']}';
942 $dependency = '['.$dependency.']';
943 $radios_attrs[$i]['data-dependency']=$dependency;
946 if ($aef->isAutoAssign()) {
947 $autoAssignTo = UserManager::instance()->getUserById($aefe->getAutoAssignto());
948 $radios_attrs[$i]=array_merge(isset($radios_attrs[$i]) ? $radios_attrs[$i] : array(), array('data-autoassignto'=>'{"id":'.$aefe->getAutoAssignto().', "name":"'.$autoAssignTo->getRealName().' ('.$autoAssignTo->getUnixName().')"}'));
951 if ($show_100 && !empty($aefChildren)) {
952 $attrs_100 = array('data-dependency'=>'{"fields": ['.implode(', ', $aefChildren).']}');
954 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);
958 * renderCheckbox - this function builds checkboxes.
960 * @param int $extra_field_id The ID of this field.
961 * @param array $checked The items that should be checked
962 * @param bool|string $show_100 Whether to show the '100 row'
963 * @param string $text_100 What to call the '100 row'
964 * @param array $attrs Array of other attributes
965 * @return string radio buttons
967 function renderCheckbox($extra_field_id, $checked=array(), $show_100=false, $text_100='none', $allowed=false, $attrs = array()) {
968 if ($text_100 == 'none'){
972 if (!$checked || !is_array($checked)) {
975 if (!empty($attrs['title'])) {
976 $attrs['title'] = util_html_secure($attrs['title']);
979 $arr = $this->getExtraFieldElements($extra_field_id);
980 $aef = new ArtifactExtraField($this, $extra_field_id);
981 $aefChildren = $aef->getChildren();
982 if (!empty($aefChildren)) {
983 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'with-depcy';
988 $chk_attrs = array();
989 $attrs_100 = array();
991 for ($i=0; $i<count($arr); $i++) {
992 $aefe = new ArtifactExtraFieldElement($aef, $arr[$i]['element_id']);
993 $vals[$i] = $arr[$i]['element_id'];
994 $texts[$i] = $arr[$i]['element_name'];
995 $chk_attrs[$i] = array();
997 if (!empty($aefChildren)) {
998 $cElmntArr = $aefe->getChildrenElements();
999 if (!empty($cElmntArr))
1001 foreach ($cElmntArr as $key=>$cElmnt) {
1002 $childField = new ArtifactExtraField($this, $key);
1003 $dependency .= (empty($dependency) ? '':', ').'{"field":'.$key.', "elmnt": ['.implode(', ', $cElmnt).']}';
1005 $dependency = '['.$dependency.']';
1006 $chk_attrs[$i]['data-dependency']=$dependency;
1010 if ($show_100 && !empty($aefChildren)) {
1011 $attrs_100['data-dependency'] ='{"fields": ['.implode(', ', $aefChildren).']}';
1013 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);
1017 * renderMultiSelectBox - this function builds checkboxes.
1019 * @param int $extra_field_id The ID of this field.
1020 * @param array $checked The items that should be checked
1021 * @param bool|string $show_100 Whether to show the '100 row'
1022 * @param string $text_100 What to call the '100 row'
1023 * @param array $attrs Array of other attributes
1024 * @return string radio multiselectbox
1026 function renderMultiSelectBox($extra_field_id, $checked=array(), $show_100=false, $text_100='none', $allowed=false, $attrs=array()) {
1030 if (!is_array($checked)) {
1031 $checked = explode(',', $checked);
1033 $arr = $this->getExtraFieldElements($extra_field_id);
1034 $aef = new ArtifactExtraField($this, $extra_field_id);
1035 $aefChildren = $aef->getChildren();
1036 if (!empty($aefChildren)) {
1037 $attrs['class'] = (empty($attrs['class']) ? '':$attrs['class'].' ').'with-depcy';
1041 $opt_attrs = array();
1042 $attrs_100 = array();
1044 for ($i=0; $i<count($arr); $i++) {
1045 $vals[$i]=$arr[$i]['element_id'];
1046 $texts[$i]=$arr[$i]['element_name'];
1047 $aefe = new ArtifactExtraFieldElement($aef, $arr[$i]['element_id']);
1048 if (!empty($aefChildren)) {
1049 $cElmntArr = $aefe->getChildrenElements();
1050 if (!empty($cElmntArr))
1053 foreach ($cElmntArr as $key=>$cElmnt) {
1054 $childField = new ArtifactExtraField($this, $key);
1055 $dependency .= (empty($dependency) ? '':', ').'{"field":'.$key.', "elmnt": ['.implode(', ', $cElmnt).']}';
1057 $dependency = '['.$dependency.']';
1058 $opt_attrs[$i]= array('data-dependency'=>$dependency);
1062 $size = min(count($arr)+1, 15);
1063 if ($show_100 && !empty($aefChildren)) {
1064 $attrs_100 = array('data-dependency'=>'{"fields": ['.implode(', ', $aefChildren).']}');
1066 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);
1070 * renderTextField - this function builds a text field.
1072 * @param int $extra_field_id The ID of this field.
1073 * @param string $contents The data for this field.
1074 * @param string $size
1075 * @param string $maxlength
1076 * @param array $attrs Array of other attributes
1077 * @return string HTML code of corresponding input tag.
1079 function renderTextField($extra_field_id, $contents, $size, $maxlength, $attrs = array()) {
1080 return html_e('input', array_merge(array('type'=>'text', 'name'=>'extra_fields['.$extra_field_id.']', 'value'=>$contents, 'size'=>$size, 'maxlength'=>$maxlength), $attrs));
1084 * renderIntegerField - this function builds a text field.
1086 * @param int $extra_field_id The ID of this field.
1087 * @param string $contents The data for this field.
1088 * @param string $size
1089 * @param string $maxlength
1090 * @param array $attrs Array of other attributes
1091 * @return string HTML code of corresponding input tag.
1093 function renderIntegerField($extra_field_id, $contents, $size, $maxlength, $attrs = array()) {
1094 $intAttrs = array('type'=>'number', 'name'=>'extra_fields['.$extra_field_id.']', 'value'=>$contents, 'size'=>$size, 'maxlength'=>$maxlength, 'min'=>0);
1095 $newattrs = array_merge($intAttrs, $attrs);
1096 return html_e('input', $newattrs);
1100 * renderRelationField - this function builds a relation field.
1102 * @param int $extra_field_id The ID of this field.
1103 * @param string $contents The data for this field.
1104 * @param string $size
1105 * @param string $maxlength
1106 * @param array $attrs Array of other attributes
1107 * @return string text area and data.
1109 function renderRelationField($extra_field_id, $contents, $size, $maxlength, $attrs = array()) {
1110 $arr = $this->getExtraFieldElements($extra_field_id);
1111 for ($i=0; $i<count($arr); $i++) {
1112 $keys[$i]=$arr[$i]['element_id'];
1113 $vals[$i]=$arr[$i]['element_name'];
1115 // Convert artifact id to links.
1116 $html_contents = preg_replace_callback('/\b(\d+)\b/', create_function('$matches', 'return _artifactid2url($matches[1], \'title\');'), $contents);
1117 $edit_contents = $this->renderTextField ($extra_field_id, $contents, $size, $maxlength);
1118 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)
1119 .html_e('div',array_merge(array('id'=>'show'.$extra_field_id, 'style'=>'display: block'), $attrs), $html_contents);
1123 * renderTextArea - this function builds a text area.
1125 * @param int $extra_field_id The ID of this field.
1126 * @param string $contents The data for this field.
1127 * @param string $rows
1128 * @param string $cols
1129 * @param array $attrs Array of other attributes
1130 * @return string text area and data.
1132 function renderTextArea($extra_field_id, $contents, $rows, $cols, $attrs = array()) {
1133 return html_e('textarea', array_merge(array('name'=>'extra_fields['.$extra_field_id.']', 'rows'=>$rows, 'cols'=>$cols), $attrs), $contents, false);
1137 * renderDatetime - this function builds a Datetime field.
1139 * @param int $extra_field_id The ID of this field.
1140 * @param string $datetime datetime for this field.
1141 * @param array $attrs Array of other attributes
1142 * @return string datetime.
1144 function renderDatetime($extra_field_id, $datetime, $attrs = array()) {
1145 if (!$datetime=='') {
1146 $datetime_format = _('Y-m-d H:i');
1147 $datetime = date($datetime_format, $datetime);
1149 if (isset($attrs['class'])) {
1150 $attrs['class'] = $attrs['class'] . ' datetimepicker';
1152 $attrs['class'] = 'datetimepicker';
1154 return html_e('input', array_merge(array('type'=>'text', 'name'=>'extra_fields['.$extra_field_id.']', 'value'=>$datetime), $attrs));
1157 function renderDateRange($extra_field_id, $dateRange, $attrs = array()) {
1158 // http://html5pattern.com/Dates
1159 // Date with leapyear-check
1160 $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)))';
1161 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));
1165 * renderEffort - this function builds a Effort extra field.
1167 * @param int $extra_field_id The ID of this field.
1168 * @param string $contents The data for this field.
1169 * @param string $size
1170 * @param string $maxlength
1171 * @param array $attrs Array of other attributes
1172 * @return string text area and data.
1174 function renderEffort($extra_field_id, $contents, $size, $maxlength, $attrs = array()) {
1175 $effortUnitSet = New EffortUnitSet($this, $this->getEffortUnitSet());
1176 $effortUnitFactory = New EffortUnitFactory($effortUnitSet);
1177 $units = $effortUnitFactory->getUnits();
1181 foreach ($units as $unit) {
1182 $vals [] = $unit->getID();
1183 $texts [] = $unit->getName();
1184 $opts_attrs []['data-factor'] = $unit->getConversionFactorForBaseUnit();
1186 $valueInUnitBase = $effortUnitFactory->encodedToValueInBaseUnit($contents);
1187 $value = $effortUnitFactory->encodedToValue($contents);
1188 $unitId = $effortUnitFactory->encodedToUnitId($contents);
1189 if (isset($attrs['class'])) {
1190 $attrs['class'] .= ' effort';
1192 $attrs['class'] = 'effort';
1194 $attrs['data-effortid'] = $extra_field_id;
1195 $hiddenAttrs = array();
1196 if (isset($attrs['form'])) {
1197 $hiddenAttrs['form'] = $attrs['form'];
1199 $return = html_e('input', array_merge(array('type'=>'hidden', 'name'=>'extra_fields['.$extra_field_id.']', 'value'=>$valueInUnitBase.'U'.$unitId), $hiddenAttrs));
1200 $return .= html_e('input', array_merge(array('type'=>'number', 'name'=>'value['.$extra_field_id.']', 'value'=>$value, 'size'=>$size, 'maxlength'=>$maxlength, 'min'=>0), $attrs));
1201 $return .= html_build_select_box_from_arrays($vals, $texts, 'unit['.$extra_field_id.']', $unitId, false, '', false, '', false, $attrs, $opts_attrs);
1205 function renderEffortRange($extra_field_id, $contents, $size, $maxlength, $attrs = array()) {
1206 $effortUnitSet = New EffortUnitSet($this, $this->getEffortUnitSet());
1207 $effortUnitFactory = New EffortUnitFactory($effortUnitSet);
1208 $units = $effortUnitFactory->getUnits();
1211 $opts_attrs = array();
1212 foreach ($units as $unit) {
1213 $vals [] = $unit->getID();
1214 $texts [] = $unit->getName();
1215 $opts_attrs []['data-factor'] = $unit->getConversionFactorForBaseUnit();
1217 $contentsFrom = '0U'.$vals[0];
1218 $contentsTo = '0U'.$vals[0];
1219 if (preg_match('/^(\d+U\d+) ?(\d+U\d+)?$/', $contents, $matches)) {
1220 $contentsFrom = $matches[1];
1221 $contentsTo = $matches[2];
1223 $valueInUnitBaseFrom = $effortUnitFactory->encodedToValueInBaseUnit($contentsFrom);
1224 $valueInUnitBaseTo = $effortUnitFactory->encodedToValueInBaseUnit($contentsTo);
1225 $valueFrom = $effortUnitFactory->encodedToValue($contentsFrom);
1226 $valueTo = $effortUnitFactory->encodedToValue($contentsTo);
1227 $unitIdFrom = $effortUnitFactory->encodedToUnitId($contentsFrom);
1228 $unitIdTo = $effortUnitFactory->encodedToUnitId($contentsTo);
1229 $attrs['data-effortid'] = $extra_field_id;
1230 if (isset($attrs['class'])) {
1231 $attrs['class'] .= ' effort-range';
1233 $attrs['class'] = 'effort-range';
1235 $attrsFrom = $attrs;
1237 $hiddenAttrs = array();
1238 if (isset($attrs['form'])) {
1239 $hiddenAttrs['form'] = $attrs['form'];
1241 $return = html_e('input', array_merge(array('type'=>'hidden', 'name'=>'extra_fields['.$extra_field_id.']', 'value'=>$valueInUnitBaseFrom.'U'.$unitIdFrom.' '.$valueInUnitBaseTo.'U'.$unitIdTo), $hiddenAttrs));
1242 $return .= _('Between')._(':').html_e('br');
1243 $return .= html_e('input', array_merge(array('type'=>'number', 'name'=>'value_from['.$extra_field_id.']', 'value'=>$valueFrom, 'size'=>$size, 'maxlength'=>$maxlength, 'min'=>0), $attrsFrom));
1244 $return .= html_build_select_box_from_arrays($vals, $texts, 'unit_from['.$extra_field_id.']', $unitIdFrom, false, '', false, '', false, $attrsFrom, $opts_attrs);
1245 $return .= html_e('br');
1246 $return .= _('and')._(':').html_e('br');
1247 $return .= html_e('input', array_merge(array('type'=>'number', 'name'=>'value_to['.$extra_field_id.']', 'value'=>$valueTo, 'size'=>$size, 'maxlength'=>$maxlength, 'min'=>0), $attrsTo));
1248 $return .= html_build_select_box_from_arrays($vals, $texts, 'unit_to['.$extra_field_id.']', $unitIdTo, false, '', false, '', false, $attrsTo, $opts_attrs);
1252 function technicianBox($name = 'assigned_to[]', $checked = 'xzxz', $show_100 = true, $text_100 = 'none', $extra_id = '-1', $extra_name = '', $multiple = false, $attrs = array()) {
1253 if ($text_100=='none'){
1254 $text_100=_('Nobody');
1256 $engine = RBACEngine::getInstance();
1257 $techs = $engine->getUsersByAllowedAction('tracker', $this->getID(), 'tech') ;
1262 sortUserList($techs);
1264 foreach ($techs as $tech) {
1265 $ids[] = $tech->getID() ;
1266 $names[] = $tech->getRealName() ;
1269 if ($extra_id != '-1') {
1271 $names[]=$extra_name;
1275 if (!is_array($checked)) {
1276 $checked = explode(',', $checked);
1278 $size = min(count($ids)+1, 15);
1279 return html_build_multiple_select_box_from_arrays($ids, $names, $name, $checked, $size, $show_100, $text_100, false, $attrs);
1281 return html_build_select_box_from_arrays($ids, $names, $name, $checked, $show_100, $text_100, false, false, false, $attrs);
1285 function submitterBox($name = 'submitted_by[]', $checked = 'xzxz', $show_100 = true, $text_100 = 'none', $extra_id = '-1', $extra_name = '', $multiple = false, $attrs = array()) {
1286 if ($text_100=='none'){
1287 $text_100=_('Nobody');
1289 $result = $this->getSubmitters();
1290 $ids =& util_result_column_to_array($result,0);
1291 $names =& util_result_column_to_array($result,1);
1292 if ($extra_id != '-1') {
1294 $names[]=$extra_name;
1298 if (!is_array($checked)) {
1299 $checked = explode(',', $checked);
1301 $size = min(count($ids)+1, 15);
1302 return html_build_multiple_select_box_from_arrays($ids, $names, $name, $checked, $size, $show_100, $text_100, false, $attrs);
1304 return html_build_select_box_from_arrays($ids, $names, $name, $checked, $show_100, $text_100, false, $attrs);
1308 function lastModifierBox($name='last_modified_by[]', $checked='xzxz', $show_100=true, $text_100='none', $extra_id='-1', $extra_name='', $multiple=false) {
1309 if ($text_100=='none'){
1310 $text_100=_('Nobody');
1312 $result = $this->getLastModifiers();
1313 $ids =& util_result_column_to_array($result,0);
1314 $names =& util_result_column_to_array($result,1);
1315 if ($extra_id != '-1') {
1317 $names[]=$extra_name;
1321 if (!is_array($checked)) {
1322 $checked = explode(',', $checked);
1324 $size = min(count($ids)+1, 15);
1325 return html_build_multiple_select_box_from_arrays($ids, $names, $name, $checked, $size, $show_100, $text_100);
1327 return html_build_select_box_from_arrays($ids, $names, $name, $checked, $show_100, $text_100);
1331 function cannedResponseBox($name = 'canned_response', $checked = 'xzxz', $attrs = array()) {
1332 if (!isset($attrs['id'])) {
1333 $attrs['id'] = $name;
1335 return html_build_select_box($this->getCannedResponses(), $name, $checked, true, 'none', false, '', false, $attrs);
1339 * statusBox - show the statuses - automatically shows the "custom statuses" if they exist
1341 * @return string HTML code
1343 function statusBox($name = 'status_id', $checked = 'xzxz', $show_100 = false, $text_100 = 'none', $attrs = array()) {
1344 if ($text_100=='none'){
1345 $text_100=_('None');
1347 return html_build_select_box($this->getStatuses(), $name, $checked, $show_100, $text_100, false, '', false, $attrs);
1351 * priorityBox - show the priorities
1353 * @return string HTML code
1355 function priorityBox($name = 'priority', $checked_val = '3', $nochange = false, $attrs = array(), $show_any = false){
1356 return html_build_priority_select_box($name, $checked_val, $nochange, $attrs, $show_any);
1359 function javascript() {
1361 var invalidSelectMsg = '"._("One or more of the selected options is not allowed")."';
1362 var invalidInputMsg = '". _("This choice is not allowed")."';
1363 var groupId =".$this->Group->getID().";
1364 var atId = ".$this->getID().";";
1366 $effortUnitSet = New EffortUnitSet($this, $this->getEffortUnitSet());
1367 if ($effortUnitSet->isAutoconvert()) {
1368 $jseffort = <<<'EOS'
1369 $("select.effort").on('change', function(){
1370 var effortid = $(this).data("effortid");
1371 var value = parseInt(parseInt($("input[name='extra_fields["+effortid+"]']").val())/$("select[name='unit["+effortid+"]'] option:selected").data('factor'));
1372 $("input[name='value["+effortid+"]']").val(value);
1373 $("input[name='extra_fields["+effortid+"]']").val(value*$("select[name='unit["+effortid+"]'] option:selected").data('factor')+'U'+$("select[name='unit["+effortid+"]']").val());
1375 $("input.effort").on('change', function(){
1376 var effortid = $(this).data("effortid");
1377 var value = $("input[name='value["+effortid+"]']").val();
1378 $("input[name='extra_fields["+effortid+"]']").val(value*$("select[name='unit["+effortid+"]'] option:selected").data('factor')+'U'+$("select[name='unit["+effortid+"]']").val());
1380 $(".effort-range").on('change', function(){
1381 var effortid = $(this).data("effortid");
1382 $("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());
1386 $jseffort = <<<'EOS'
1387 $(".effort").on('change', function(){
1388 var effortid = $(this).data("effortid");
1389 $("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());
1391 $(".effort-range").on('change', function(){
1392 var effortid = $(this).data("effortid");
1393 $("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());
1397 $javascript = <<<'EOS'
1398 function showMessage( msg_text, msg_class) {
1399 $("div#maindiv h1").append($("<p>", { "class": msg_class }).html( msg_text )).show();
1401 $.expr[':'].invalid = function(elem, index, match) {
1402 for (let invalid of document.querySelectorAll(':invalid') ) {
1403 if (elem === invalid) { return true; }
1407 $("input[type='radio'].readonly, input[type='checkbox'].readonly").on('click', function(){
1409 }).on('keydown', function(event){
1410 if(event.keyCode !== 9) return false;
1412 $(".in-formula").on('change', function(){
1416 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(),
1419 success: function(answer){
1420 if(answer['message']) {
1421 showMessage(answer['message'], 'error');
1423 fields = answer['fields'];
1424 $.each(fields, function (index, field) {
1425 if (field.error!=null){
1426 showMessage(field.error, 'error');
1428 fieldObj = $("[name^='extra_fields["+field.id+"]']");
1429 if (fieldObj.is("input[type='checkbox']")){
1430 fieldObj.each(function() {
1432 for (var key in field.value) {
1433 if (field.value[key] == $(this).val()) {
1438 if (in_array > -1) {
1439 $(this).prop("checked",true);
1441 $(this).prop("checked",false);
1444 } else if (fieldObj.is("input[type='radio']")){
1445 fieldObj.each(function() {
1447 for (var key in field.value) {
1448 if (field.value[key] == $(this).val()) {
1453 if (in_array > -1) {
1454 $(this).prop("checked",true);
1456 $(this).prop("checked",false);
1459 } else if (fieldObj.is("input")){
1460 fieldObj.val(field.value);
1461 } else if (fieldObj.is("select")){
1462 fieldObj.val(field.value);
1463 } else if (fieldObj.is("textarea")){
1464 fieldObj.val(field.value);
1471 $(".autoassign[name^='extra_fields']").on('change', function(){
1472 if ($(this).prop('tagName') == 'SELECT') {
1473 var elmnts = $(this).children('option:selected');
1475 var elmnts = $(this).siblings('input:checked');
1477 elmnts.each(function(i){
1478 var aat = $(this).data("autoassignto");
1479 $("select#tracker-assigned_to option[value="+aat.id+"]").prop('selected', true);
1480 $("span#tracker-assigned_to").text(aat.name);
1483 $(".with-depcy[name^='extra_fields']").on('change', function(){
1484 if ($(this).prop('tagName') == 'SELECT') {
1485 var elmnts = $(this).children('option:selected');
1487 var elmnts = $(this).siblings('input:checked');
1489 elmnts.each(function(i){
1490 var dep = $(this).data("dependency");
1491 if (this.value!='100') {
1492 $(dep).each(function(j, val) {
1493 $("select[name^='extra_fields["+val.field+"]']:invalid, input[name^='extra_fields["+val.field+"]']:invalid").each(function() {
1494 this.setCustomValidity("");
1495 $(this).off("change.invalid");
1497 $("select[name^='extra_fields["+val.field+"]'] option").each(function(k,opt){
1498 if (this.value!='100') {
1499 if ($.inArray(parseInt(this.value),val.elmnt)>-1) {
1500 $(this).prop('disabled', false).removeClass('option_disabled');
1502 $(this).prop('disabled', true);
1503 $(this).addClass('option_disabled');
1507 $("input[name^='extra_fields["+val.field+"]']").each(function(k,opt){
1508 if (this.value!='100') {
1509 if ($.inArray(parseInt(this.value),val.elmnt)>-1) {
1510 $(this).prop('disabled', false).removeClass($(this).attr('type')+'_disabled');
1512 $(this).prop('disabled', true);
1513 $(this).addClass($(this).attr('type')+'_disabled');
1519 $(dep.fields).each(function(j, val) {
1520 $("select[name^='extra_fields["+val+"]']:invalid, input[name^='extra_fields["+val+"]']:invalid").each(function() {
1521 this.setCustomValidity("");
1523 $("select[name^='extra_fields["+val+"]'] option.option_disabled").each(function() {
1524 $(this).prop('disabled', false).removeClass('option_disabled');
1526 $("input.radio_disable[name^='extra_fields["+val+"]']").each(function() {
1527 $(this).prop('disabled', false).removeClass('radio_disabled');
1529 $("input.checkbox_disabled[name^='extra_fields["+val+"]']").each(function() {
1530 $(this).prop('disabled', false).removeClass('checkbox_disabled');
1535 $("select[name^='extra_fields'] option:selected:disabled").parent().each(function() {
1536 $(this).children('option:selected:disabled').prop('disabled', false);
1537 this.setCustomValidity(invalidSelectMsg);
1538 $(this).on("change.invalid", function() {
1539 $(this).children('option.option_disabled:not(:disabled):not(:selected)').prop('disabled', true);
1540 if (!$(this).children('option.option_disabled:selected').length) {
1541 this.setCustomValidity("");
1542 $(this).off("change.invalid");
1546 $("input[name^='extra_fields']:checked:disabled").each(function() {
1547 $(this).prop('disabled', false);
1548 this.setCustomValidity(invalidInputMsg);
1549 if ($(this).attr('type') == 'radio') {
1550 $(this).siblings('input[type="radio"]').on("change.invalid", function() {
1551 $(this).siblings('input[type="radio"]:invalid').prop('disabled', true).addClass('input_disabled').each(function() {
1552 this.setCustomValidity("");
1554 $(this).siblings('input[type="radio"]').off("change.invalid");
1555 $(this).off("change.invalid");
1558 $(this).on("change.invalid", function() {
1559 $(this).prop('disabled', true);
1560 this.setCustomValidity("");
1561 $(this).off("change.invalid");
1567 return html_e('script', array('type'=>'text/javascript'), '//<![CDATA['."\n".'$(function(){'.$jsvariable."\n".$jseffort."\n".$javascript.'});'."\n".'//]]>');
1574 // c-file-style: "bsd"