- Edited
I was trying to add a column to the task menu, I saw that there were many posts about it and none of them could solve it, I share what I was able to do by myself.
I'm sure this isn't the best approach, so any suggestions are welcome. Also, my first language is Spanish so you may see some text in it
First create the field in the task form menu, remember to complete the "Variable" field, you will use it later to show the data in this case is "time"
This custom fileds are stored in the table "ost_task__cdata" and as you can see the column name is "time"
Then edit the file include/staff/tasks.inc.php
Here is my final file
1. <?php
2. $tasks = Task::objects();
3. $date_header = $date_col = false;
4.
5. // Make sure the cdata materialized view is available
6. TaskForm::ensureDynamicDataView();
7.
8. // Figure out REFRESH url — which might not be accurate after posting a
9. // response
10. list($path,) = explode('?', $_SERVER['REQUEST_URI'], 2);
11. $args = array();
12. parse_str($_SERVER['QUERY_STRING'], $args);
13.
14. // Remove commands from query
15. unset($args['id']);
16. unset($args['a']);
17.
18. $refresh_url = htmlspecialchars($path) . '?' . http_build_query($args);
19.
20. $sort_options = array(
21. 'updated' => __('Most Recently Updated'),
22. 'created' => __('Most Recently Created'),
23. 'due' => __('Due Date'),
24. 'number' => __('Task Number'),
25. 'closed' => __('Most Recently Closed'),
26. 'hot' => __('Longest Thread'),
27. 'relevance' => __('Relevance'),
28. );
29.
30. // Queues columns
31.
32. $queue_columns = array(
33. 'number' => array(
34. 'width' => '8%',
35. 'heading' => __('Number'),
36. ),
37. 'ticket' => array(
38. 'width' => '16%',
39. 'heading' => __('Ticket'),
40. 'sort_col' => 'ticket__number',
41. ),
42. 'date' => array(
43. 'width' => '20%',
44. 'heading' => __('Date Created'),
45. 'sort_col' => 'created',
46. ),
47. 'title' => array(
48. 'width' => '38%',
49. 'heading' => __('Title'),
50. 'sort_col' => 'cdata__title',
51. ),
52. 'dept' => array(
53. 'width' => '16%',
54. 'heading' => __('Department'),
55. 'sort_col' => 'dept__name',
56. ),
57. 'assignee' => array(
58. 'width' => '16%',
59. 'heading' => __('Agent'),
60. ),
61. 'time' => array(
62. 'width' => '16%',
63. 'heading' => __('Tiempo'),
64. ),
65. );
66.
67.
68.
69. // Queue we're viewing
70. $queue_key = sprintf('::Q:%s', ObjectModel::OBJECT_TYPE_TASK);
71. $queue_name = $_SESSION[$queue_key] ?: '';
72.
73. switch ($queue_name) {
74. case 'closed':
75. $status='closed';
76. $results_type=__('Completed Tasks');
77. $showassigned=true; //closed by.
78. $queue_sort_options = array('closed', 'updated', 'created', 'number', 'hot');
79.
80. break;
81. case 'overdue':
82. $status='open';
83. $results_type=__('Overdue Tasks');
84. $tasks->filter(array('isoverdue'=>1));
85. $queue_sort_options = array('updated', 'created', 'number', 'hot');
86. break;
87. case 'assigned':
88. $status='open';
89. $staffId=$thisstaff->getId();
90. $results_type=__('My Tasks');
91. $tasks->filter(array('staff_id'=>$thisstaff->getId()));
92. $queue_sort_options = array('updated', 'created', 'hot', 'number');
93. break;
94. default:
95. case 'search':
96. $queue_sort_options = array('closed', 'updated', 'created', 'number', 'hot');
97. // Consider basic search
98. if ($_REQUEST['query']) {
99. $results_type=__('Search Results');
100. $tasks = $tasks->filter(Q::any(array(
101. 'number__startswith' => $_REQUEST['query'],
102. 'cdata__title__contains' => $_REQUEST['query'],
103. )));
104. unset($_SESSION[$queue_key]);
105. break;
106. }
107. // Fall-through and show open tickets
108. case 'open':
109. $status='open';
110. $results_type=__('Open Tasks');
111. $queue_sort_options = array('created', 'updated', 'due', 'number', 'hot');
112. break;
113. }
114.
115. // Apply filters
116. $filters = array();
117. if ($status) {
118. $SQ = new Q(array('flags__hasbit' => TaskModel::ISOPEN));
119. if (!strcasecmp($status, 'closed'))
120. $SQ->negate();
121.
122. $filters[] = $SQ;
123. }
124.
125. if ($filters)
126. $tasks->filter($filters);
127.
128. // Impose visibility constraints
129. // ------------------------------------------------------------
130. // -- Open and assigned to me
131. $visibility = Q::any(
132. new Q(array('flags__hasbit' => TaskModel::ISOPEN, 'staff_id' => $thisstaff->getId()))
133. );
134. // -- Task for tickets assigned to me
135. $visibility->add(new Q( array(
136. 'ticket__staff_id' => $thisstaff->getId(),
137. 'ticket__status__state' => 'open'))
138. );
139. // -- Routed to a department of mine
140. if (!$thisstaff->showAssignedOnly() && ($depts=$thisstaff->getDepts()))
141. $visibility->add(new Q(array('dept_id__in' => $depts)));
142. // -- Open and assigned to a team of mine
143. if (($teams = $thisstaff->getTeams()) && count(array_filter($teams)))
144. $visibility->add(new Q(array(
145. 'team_id__in' => array_filter($teams),
146. 'flags__hasbit' => TaskModel::ISOPEN
147. )));
148. $tasks->filter(new Q($visibility));
149.
150. // Add in annotations
151. $tasks->annotate(array(
152. 'collab_count' => SqlAggregate::COUNT('thread__collaborators', true),
153. 'attachment_count' => SqlAggregate::COUNT(SqlCase::N()
154. ->when(new SqlField('thread__entries__attachments__inline'), null)
155. ->otherwise(new SqlField('thread__entries__attachments')),
156. true
157. ),
158. 'thread_count' => SqlAggregate::COUNT(SqlCase::N()
159. ->when(
160. new Q(array('thread__entries__flags__hasbit'=>ThreadEntry::FLAG_HIDDEN)),
161. null)
162. ->otherwise(new SqlField('thread__entries__id')),
163. true
164. ),
165. ));
166.
167. $tasks->values('id', 'number', 'created', 'staff_id', 'team_id',
168. 'staff__firstname', 'staff__lastname', 'team__name',
169. 'dept__name', 'cdata__title', 'flags', 'ticket__number', 'ticket__ticket_id','cdata__time');
170. // Apply requested quick filter
171.
172. $queue_sort_key = sprintf(':Q%s:%s:sort', ObjectModel::OBJECT_TYPE_TASK, $queue_name);
173.
174. if (isset($_GET['sort'])) {
175. $_SESSION[$queue_sort_key] = array($_GET['sort'], $_GET['dir']);
176. }
177. elseif (!isset($_SESSION[$queue_sort_key])) {
178. $_SESSION[$queue_sort_key] = array($queue_sort_options[0], 0);
179. }
180.
181. list($sort_cols, $sort_dir) = $_SESSION[$queue_sort_key];
182. $orm_dir = $sort_dir ? QuerySet::ASC : QuerySet::DESC;
183. $orm_dir_r = $sort_dir ? QuerySet::DESC : QuerySet::ASC;
184.
185. switch ($sort_cols) {
186. case 'number':
187. $queue_columns['number']['sort_dir'] = $sort_dir;
188. $tasks->extra(array(
189. 'order_by'=>array(
190. array(SqlExpression::times(new SqlField('number'), 1), $orm_dir)
191. )
192. ));
193. break;
194. case 'due':
195. $queue_columns['date']['heading'] = __('Due Date');
196. $queue_columns['date']['sort'] = 'due';
197. $queue_columns['date']['sort_col'] = $date_col = 'duedate';
198. $tasks->values('duedate');
199. $tasks->order_by(SqlFunction::COALESCE(new SqlField('duedate'), 'zzz'), $orm_dir_r);
200. break;
201. case 'closed':
202. $queue_columns['date']['heading'] = __('Date Closed');
203. $queue_columns['date']['sort'] = $sort_cols;
204. $queue_columns['date']['sort_col'] = $date_col = 'closed';
205. $queue_columns['date']['sort_dir'] = $sort_dir;
206. $tasks->values('closed');
207. $tasks->order_by($sort_dir ? 'closed' : '-closed');
208. break;
209. case 'updated':
210. $queue_columns['date']['heading'] = __('Last Updated');
211. $queue_columns['date']['sort'] = $sort_cols;
212. $queue_columns['date']['sort_col'] = $date_col = 'updated';
213. $tasks->values('updated');
214. $tasks->order_by($sort_dir ? 'updated' : '-updated');
215. break;
216. case 'hot':
217. $tasks->order_by('-thread_count');
218. $tasks->annotate(array(
219. 'thread_count' => SqlAggregate::COUNT('thread__entries'),
220. ));
221. break;
222. case 'assignee':
223. $tasks->order_by('staff__lastname', $orm_dir);
224. $tasks->order_by('staff__firstname', $orm_dir);
225. $tasks->order_by('team__name', $orm_dir);
226. $queue_columns['assignee']['sort_dir'] = $sort_dir;
227. break;
228. default:
229. if ($sort_cols && isset($queue_columns[$sort_cols])) {
230. $queue_columns[$sort_cols]['sort_dir'] = $sort_dir;
231. if (isset($queue_columns[$sort_cols]['sort_col']))
232. $sort_cols = $queue_columns[$sort_cols]['sort_col'];
233. $tasks->order_by($sort_cols, $orm_dir);
234. break;
235. }
236. case 'created':
237. $queue_columns['date']['heading'] = __('Date Created');
238. $queue_columns['date']['sort'] = 'created';
239. $queue_columns['date']['sort_col'] = $date_col = 'created';
240. $tasks->order_by($sort_dir ? 'created' : '-created');
241. break;
242. }
243.
244. if (in_array($sort_cols, array('created', 'due', 'updated')))
245. $queue_columns['date']['sort_dir'] = $sort_dir;
246.
247. // Apply requested pagination
248. $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1;
249. $count = $tasks->count();
250. $pageNav=new Pagenate($count, $page, PAGE_LIMIT);
251. $pageNav->setURL('tasks.php', $args);
252. $tasks = $pageNav->paginate($tasks);
253.
254. // Save the query to the session for exporting
255. $_SESSION[':Q:tasks'] = $tasks;
256.
257. // Mass actions
258. $actions = array();
259.
260. if ($thisstaff->hasPerm(Task::PERM_ASSIGN, false)) {
261. $actions += array(
262. 'assign' => array(
263. 'icon' => 'icon-user',
264. 'action' => __('Assign Tasks')
265. ));
266. }
267.
268. if ($thisstaff->hasPerm(Task::PERM_TRANSFER, false)) {
269. $actions += array(
270. 'transfer' => array(
271. 'icon' => 'icon-share',
272. 'action' => __('Transfer Tasks')
273. ));
274. }
275.
276. if ($thisstaff->hasPerm(Task::PERM_DELETE, false)) {
277. $actions += array(
278. 'delete' => array(
279. 'icon' => 'icon-trash',
280. 'action' => __('Delete Tasks')
281. ));
282. }
283.
284.
285. ?>
286. <!-- SEARCH FORM START -->
287. <div id='basic_search'>
288. <div class="pull-right" style="height:25px">
289. <span class="valign-helper"></span>
290. <?php
291. require STAFFINC_DIR.'templates/tasks-queue-sort.tmpl.php';
292. ?>
293. </div>
294. <form action="tasks.php" method="get" onsubmit="javascript:
295. $.pjax({
296. url:$(this).attr('action') + '?' + $(this).serialize(),
297. container:'#pjax-container',
298. timeout: 2000
299. });
300. return false;">
301. <input type="hidden" name="a" value="search">
302. <input type="hidden" name="search-type" value=""/>
303. <div class="attached input">
304. <input type="text" class="basic-search" data-url="ajax.php/tasks/lookup" name="query"
305. autofocus size="30" value="<?php echo Format::htmlchars($_REQUEST['query'], true); ?>"
306. autocomplete="off" autocorrect="off" autocapitalize="off">
307. <button type="submit" class="attached button"><i class="icon-search"></i>
308. </button>
309. </div>
310. </form>
311.
312. </div>
313. <!-- SEARCH FORM END -->
314. <div class="clear"></div>
315. <div style="margin-bottom:20px; padding-top:5px;">
316. <div class="sticky bar opaque">
317. <div class="content">
318. <div class="pull-left flush-left">
319. <h2><a href="<?php echo $refresh_url; ?>"
320. title="<?php echo __('Refresh'); ?>"><i class="icon-refresh"></i> <?php echo
321. $results_type.$showing; ?></a></h2>
322. </div>
323. <div class="pull-right flush-right">
324. <?php
325. if ($count)
326. echo Task::getAgentActions($thisstaff, array('status' => $status));
327. ?>
328. </div>
329. </div>
330. </div>
331. <div class="clear"></div>
332. <form action="tasks.php" method="POST" name='tasks' id="tasks">
333. <?php csrf_token(); ?>
334. <input type="hidden" name="a" value="mass_process" >
335. <input type="hidden" name="do" id="action" value="" >
336. <input type="hidden" name="status" value="<?php echo
337. Format::htmlchars($_REQUEST['status'], true); ?>" >
338. <table class="list" border="0" cellspacing="1" cellpadding="2" width="940px">
339. <thead>
340. <tr>
341. <?php if ($thisstaff->canManageTickets()) { ?>
342. <th width="4%"> </th>
343. <?php } ?>
344.
345. <?php
346. // Query string
347. unset($args['sort'], $args['dir'], $args['_pjax']);
348. $qstr = Http::build_query($args);
349. // Show headers
350. foreach ($queue_columns as $k => $column) {
351. echo sprintf( '<th width="%s"><a href="?sort=%s&dir=%s&%s"
352. class="%s">%s</a></th>',
353. $column['width'],
354. $column['sort'] ?: $k,
355. $column['sort_dir'] ? 0 : 1,
356. $qstr,
357. isset($column['sort_dir'])
358. ? ($column['sort_dir'] ? 'asc': 'desc') : '',
359. $column['heading']);
360. }
361. ?>
362. </tr>
363. </thead>
364. <tbody>
365. <?php
366. // Setup Subject field for display
367. $total=0;
368. $title_field = TaskForm::getInstance()->getField('title');
369.
> $time_field = TaskForm::getInstance()->getField('time');
370. $ids=($errors && $_POST['tids'] && is_array($_POST['tids']))?$_POST['tids']:null;
371. foreach ($tasks as $T) {
372. $T['isopen'] = ($T['flags'] & TaskModel::ISOPEN != 0); //XXX:
373. $total += 1;
374. $tag=$T['staff_id']?'assigned':'openticket';
375. $flag=null;
376. if($T['lock__staff_id'] && $T['lock__staff_id'] != $thisstaff->getId())
377. $flag='locked';
378. elseif($T['isoverdue'])
379. $flag='overdue';
380.
381. $assignee = '';
382. $dept = Dept::getLocalById($T['dept_id'], 'name', $T['dept__name']);
383. $assinee ='';
384. if ($T['staff_id']) {
385. $staff = new AgentsName($T['staff__firstname'].' '.$T['staff__lastname']);
386. $assignee = sprintf('<span class="Icon staffAssigned">%s</span>',
387. Format::truncate((string) $staff, 40));
388. } elseif($T['team_id']) {
389. $assignee = sprintf('<span class="Icon teamAssigned">%s</span>',
390. Format::truncate(Team::getLocalById($T['team_id'], 'name', $T['team__name']),40));
391. }
392.
393. $threadcount=$T['thread_count'];
394. $number = $T['number'];
395. if ($T['isopen'])
396. $number = sprintf('<b>%s</b>', $number);
397.
398. $title = Format::truncate($title_field->display($title_field->to_php($T['cdata__title'])), 40);
399.
> $time = Format::truncate($time_field->display($time_field->to_php($T['cdata__time'])), 40);
400. echo "<script>console.log('Debug Objects: " . $time . "' );</script>";
401. ?>
402. <tr id="<?php echo $T['id']; ?>">
403. <?php
404. if ($thisstaff->canManageTickets()) {
405. $sel = false;
406. if ($ids && in_array($T['id'], $ids))
407. $sel = true;
408. ?>
409. <td align="center" class="nohover">
410. <input class="ckb" type="checkbox" name="tids[]"
411. value="<?php echo $T['id']; ?>" <?php echo $sel?'checked="checked"':''; ?>>
412. </td>
413. <?php } ?>
414. <td nowrap>
415. <a class="preview"
416. href="tasks.php?id=<?php echo $T['id']; ?>"
417. data-preview="#tasks/<?php echo $T['id']; ?>/preview"
418. ><?php echo $number; ?></a></td>
419. <td nowrap>
420. <a class="preview"
421. href="tickets.php?id=<?php echo $T['ticket__ticket_id']; ?>"
422. data-preview="#tickets/<?php echo $T['ticket__ticket_id']; ?>/preview"
423. ><?php echo $T['ticket__number']; ?></a></td>
424. <td align="center" nowrap><?php echo
425. Format::datetime($T[$date_col ?: 'created']); ?></td>
426. <td><a <?php if ($flag) { ?> class="Icon <?php echo $flag; ?>Ticket" title="<?php echo ucfirst($flag); ?> Ticket" <?php } ?>
427. href="tasks.php?id=<?php echo $T['id']; ?>"><?php
428. echo $title; ?></a>
429. <?php
430. if ($threadcount>1)
431. echo "<small>($threadcount)</small> ".'<i
432. class="icon-fixed-width icon-comments-alt"></i> ';
433. if ($T['collab_count'])
434. echo '<i class="icon-fixed-width icon-group faded"></i> ';
435. if ($T['attachment_count'])
436. echo '<i class="icon-fixed-width icon-paperclip"></i> ';
437. ?>
438. </td>
439. <td nowrap> <?php echo Format::truncate($dept, 40); ?></td>
440. <td nowrap> <?php echo $assignee; ?></td>
441.
> <td nowrap> <?php echo $time ?></td>
442. </tr>
443. <?php
444. } //end of foreach
445. if (!$total)
446. $ferror=__('There are no tasks matching your criteria.');
447. ?>
448. </tbody>
449. <tfoot>
450. <tr>
451. <td colspan="8">
452. <?php if($total && $thisstaff->canManageTickets()){ ?>
453. <?php echo __('Select');?>:
454. <a id="selectAll" href="#ckb"><?php echo __('All');?></a>
455. <a id="selectNone" href="#ckb"><?php echo __('None');?></a>
456. <a id="selectToggle" href="#ckb"><?php echo __('Toggle');?></a>
457. <?php }else{
458. echo '<i>';
459. echo $ferror?Format::htmlchars($ferror):__('Query returned 0 results.');
460. echo '</i>';
461. } ?>
462. </td>
463. </tr>
464. </tfoot>
465. </table>
466. <?php
467. if ($total>0) { //if we actually had any tasks returned.
468. echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' ';
469. echo sprintf('<a class="export-csv no-pjax" href="?%s">%s</a>',
470. Http::build_query(array(
471. 'a' => 'export', 'h' => $hash,
472. 'status' => $_REQUEST['status'])),
473. __('Export'));
474. echo ' <i class="help-tip icon-question-sign" href="#export"></i></div>';
475. } ?>
476. </form>
477. </div>
478.
479. <div style="display:none;" class="dialog" id="confirm-action">
480. <h3><?php echo __('Please Confirm');?></h3>
481. <a class="close" href=""><i class="icon-remove-circle"></i></a>
482. <hr/>
483. <p class="confirm-action" style="display:none;" id="mark_overdue-confirm">
484. <?php echo __('Are you sure want to flag the selected tasks as <font color="red"><b>overdue</b></font>?');?>
485. </p>
486. <div><?php echo __('Please confirm to continue.');?></div>
487. <hr style="margin-top:1em"/>
488. <p class="full-width">
489. <span class="buttons pull-left">
490. <input type="button" value="<?php echo __('No, Cancel');?>" class="close">
491. </span>
492. <span class="buttons pull-right">
493. <input type="button" value="<?php echo __('Yes, Do it!');?>" class="confirm">
494. </span>
495. </p>
496. <div class="clear"></div>
497. </div>
498. <script type="text/javascript">
499. $(function() {
500.
501. $(document).off('.new-task');
502. $(document).on('click.new-task', 'a.new-task', function(e) {
503. e.preventDefault();
504. var url = 'ajax.php/'
505. +$(this).attr('href').substr(1)
506. +'?_uid='+new Date().getTime();
507. var $options = $(this).data('dialogConfig');
508. $.dialog(url, [201], function (xhr) {
509. var tid = parseInt(xhr.responseText);
510. if (tid) {
511. window.location.href = 'tasks.php?id='+tid;
512. } else {
513. $.pjax.reload('#pjax-container');
514. }
515. }, $options);
516.
517. return false;
518. });
519.
520. $('[data-toggle=tooltip]').tooltip();
521. });
522. </script>