can_select_project($proj->id)) {
$proj = new Project(0);
}
$perpage = '50';
if (isset($user->infos['tasks_perpage']) && $user->infos['tasks_perpage'] > 0) {
$perpage = $user->infos['tasks_perpage'];
}
$pagenum = Get::num('pagenum', 1);
if ($pagenum < 1) {
$pagenum = 1;
}
$offset = $perpage * ($pagenum - 1);
// Get the visibility state of all columns
$visible = explode(' ', trim($proj->id ? $proj->prefs['visible_columns'] : $fs->prefs['visible_columns']));
if (!is_array($visible) || !count($visible) || !$visible[0]) {
$visible = array('id');
}
// Remove columns the user is not allowed to see
if (in_array('estimated_effort', $visible) && !$user->perms('view_estimated_effort')) {
unset($visible[array_search('estimated_effort', $visible)]);
}
if (in_array('effort', $visible) && !$user->perms('view_current_effort_done')) {
unset($visible[array_search('effort', $visible)]);
}
# for csv export no paging limits
if (Get::has('export_list')) {
$offset = -1;
$perpage = -1;
}
list($tasks, $id_list, $totalcount, $forbiddencount) = Backend::get_task_list($_GET, $visible, $offset, $perpage);
if (Get::has('export_list')) {
export_task_list();
}
$page->uses('tasks', 'offset', 'perpage', 'pagenum', 'visible');
// List of task IDs for next/previous links
# Mmh the result is persistent in $_SESSION a bit for the length of each user session and can lead to a DOS quite fast on bigger installs?
# Do we really need prev-next on task details view or can we find an alternative solution?
# And using the $_SESSION for that is currently not working correct if someone uses 2 browser tabs for 2 different projects.
$_SESSION['tasklist'] = $id_list;
$page->assign('total', $totalcount);
$page->assign('forbiddencount', $forbiddencount);
// Send user variables to the template
$result = $db->query('SELECT DISTINCT u.user_id, u.user_name, u.real_name, g.group_name, g.project_id
FROM {users} u
LEFT JOIN {users_in_groups} uig ON u.user_id = uig.user_id
LEFT JOIN {groups} g ON g.group_id = uig.group_id
WHERE (g.show_as_assignees = 1 OR g.is_admin = 1)
AND (g.project_id = 0 OR g.project_id = ?) AND u.account_enabled = 1
ORDER BY g.project_id ASC, g.group_name ASC, u.user_name ASC', ($proj->id || -1)); // FIXME: -1 is a hack. when $proj->id is 0 the query fails
$userlist = array();
while ($row = $db->fetchRow($result)) {
$userlist[$row['group_name']][] = array(0 => $row['user_id'],
1 => sprintf('%s (%s)', $row['user_name'], $row['real_name']));
}
$page->assign('userlist', $userlist);
/**
* tpl function that Displays a header cell for report list
*/
function tpl_list_heading($colname, $format = "
%s | ")
{
global $proj, $page;
$imgbase = '';
$class = $colname;
$html = eL($colname);
/*
if ($colname == 'comments' || $colname == 'attachments') {
$html = sprintf($imgbase, $page->get_image(substr($colname, 0, -1)), $html);
}
*/
if ($colname == 'attachments') {
$html='';
}
if ($colname == 'comments') {
$html='';
}
if ($colname == 'votes') {
$html='';
}
if (Get::val('order') == $colname) {
$class .= ' orderby';
$sort1 = Get::safe('sort', 'desc') == 'desc' ? 'asc' : 'desc';
$sort2 = Get::safe('sort2', 'desc');
$order2 = Get::safe('order2');
$html .= ' '.sprintf($imgbase, $page->get_image(Get::val('sort')), Get::safe('sort'));
}
else {
$sort1 = 'desc';
if (in_array($colname,
array('project', 'tasktype', 'category', 'openedby', 'assignedto')))
{
$sort1 = 'asc';
}
$sort2 = Get::safe('sort', 'desc');
$order2 = Get::safe('order');
}
$new_order = array('order' => $colname, 'sort' => $sort1, 'order2' => $order2, 'sort2' => $sort2);
# unneeded params from $_GET for the sort links
$params=array_merge($_GET, $new_order);
unset($params['do']);
unset($params['project']);
unset($params['switch']);
$html = sprintf('%s',
eL('sortthiscolumn'), Filters::noXSS(createURL('tasklist', $proj->id, null, $params )), $html);
return sprintf($format, ' class="'.$class.'"', $html);
}
/**
* tpl function that draws a cell
*/
function tpl_draw_cell($task, $colname, $format = "%s | ") {
global $fs, $db, $proj, $page, $user;
$indexes = array (
'id' => 'task_id',
'project' => 'project_title',
'tasktype' => 'task_type',
'tasktypename'=> 'tasktype_name',
'category' => 'category_name',
'severity' => '',
'priority' => '',
'summary' => 'item_summary',
'dateopened' => 'date_opened',
'status' => 'status_name',
'openedby' => 'opened_by',
'openedbyname'=> 'opened_by_name',
'assignedto' => 'assigned_to_name',
'lastedit' => 'max_date',
'editedby' => 'last_edited_by',
'reportedin' => 'product_version_name',
'dueversion' => 'closedby_version_name',
'duedate' => 'due_date',
'comments' => 'num_comments',
'votes' => 'num_votes',
'attachments'=> 'num_attachments',
'dateclosed' => 'date_closed',
'closedby' => 'closed_by',
'commentedby'=> 'commented_by',
'progress' => '',
'os' => 'os_name',
'private' => 'mark_private',
'parent' => 'supertask_id',
'estimatedeffort' => 'estimated_effort',
);
//must be an array , must contain elements and be alphanumeric (permitted "_")
if(!is_array($task) || empty($task) || preg_match('![^A-Za-z0-9_]!', $colname)) {
//run away..
return '';
}
$class= 'task_'.$colname;
switch ($colname) {
case 'id':
$value = tpl_tasklink($task, $task['task_id']);
break;
case 'summary':
$value = tpl_tasklink($task, utf8_substr($task['item_summary'], 0, 55));
if (utf8_strlen($task['item_summary']) > 55) {
$value .= '...';
}
if($task['tagids']!=''){
#$tags=explode(',', $task['tags']);
$tagids=explode(',', $task['tagids']);
#$tagclass=explode(',', $task['tagclass']);
$tgs='';
for($i=0;$i< count($tagids); $i++){
$tgs.=tpl_tag($tagids[$i]);
}
$value.=$tgs;
}
break;
case 'tasktype':
$value = htmlspecialchars($task['tasktype_name'], ENT_QUOTES, 'utf-8');
$class.=' typ'.$task['task_type'];
break;
case 'severity':
$value = $task['task_severity']==0 ? '' : $fs->severities[$task['task_severity']];
$class.=' sev'.$task['task_severity'];
break;
case 'priority':
$value = $task['task_priority']==0 ? '' : $fs->priorities[$task['task_priority']];
$class.=' pri'.$task['task_priority'];
break;
case 'attachments':
case 'comments':
case 'votes':
$value = $task[$indexes[$colname]]>0 ? $task[$indexes[$colname]]:'';
break;
case 'lastedit':
case 'duedate':
case 'dateopened':
case 'dateclosed':
$value = formatDate($task[$indexes[$colname]]);
break;
case 'status':
if ($task['is_closed']) {
$value = eL('closed');
} else {
$value = htmlspecialchars($task[$indexes[$colname]], ENT_QUOTES, 'utf-8');
}
$class.=' sta'.$task['item_status'];
break;
case 'progress':
$value = tpl_img($page->get_image('percent-' . $task['percent_complete'], false),
$task['percent_complete'] . '%');
break;
case 'assignedto':
# group_concat-ed for mysql/pgsql
#$value = htmlspecialchars($task[$indexes[$colname]], ENT_QUOTES, 'utf-8');
$value='';
$anames=explode(',',$task[$indexes[$colname]]);
$aids=explode(',',$task['assignedids']);
$aimages=explode(',',$task['assigned_image']);
for($a=0;$a < count($anames);$a++){
if($aids[$a]){
# deactivated: avatars looks too ugly in the tasklist, user's name needs to be visible on a first look here, without needed mouse hovering..
#if ($fs->prefs['enable_avatars']==1 && $aimages[$a]){
# $value.=tpl_userlinkavatar($aids[$a],30);
#} else{
$value.=tpl_userlink($aids[$a]);
#}
#$value.=''.htmlspecialchars($anames[$a], ENT_QUOTES, 'utf-8').'';
}
}
# fallback for DBs we haven't written sql string aggregation yet (currently with group_concat() mysql and array_agg() postgresql)
if( ('postgres' != $db->dblink->dataProvider) && ('mysql' != $db->dblink->dataProvider) && ($task['num_assigned'] > 1)) {
$value .= ', +' . ($task['num_assigned'] - 1);
}
break;
case 'private':
$value = $task[$indexes[$colname]] ? L('yes') : L('no');
break;
case 'commentedby':
case 'openedby':
case 'editedby':
case 'closedby':
$value = '';
# a bit expensive! tpl_userlinkavatar() an additional sql query for each new user in the output table
# at least tpl_userlink() uses a $cache array so query for repeated users
if ($task[$indexes[$colname]] > 0) {
# deactivated: avatars looks too ugly in the tasklist, user's name needs to be visible on a first look here, without needed mouse hovering..
#if ($fs->prefs['enable_avatars']==1){
# $value = tpl_userlinkavatar($task[$indexes[$colname]],30);
#} else{
$value = tpl_userlink($task[$indexes[$colname]]);
#}
}
break;
case 'parent':
$value = '';
if ($task['supertask_id'] > 0) {
$value = tpl_tasklink($task, $task['supertask_id']);
}
break;
case 'estimatedeffort':
$value = '';
if ($user->perms('view_estimated_effort')) {
if ($task['estimated_effort'] > 0){
$value = effort::secondsToString($task['estimated_effort'], $proj->prefs['hours_per_manday'], $proj->prefs['estimated_effort_format']);
}
}
break;
case 'effort':
$value = '';
if ($user->perms('view_current_effort_done')) {
if ($task['effort'] > 0){
$value = effort::secondsToString($task['effort'], $proj->prefs['hours_per_manday'], $proj->prefs['current_effort_done_format']);
}
}
break;
default:
$value = '';
// $colname here is NOT column name in database but a name that can appear
// both in a projects visible fields and as a key in language translation
// file, which is also used to draw a localized heading. Column names in
// database customarily use _ t to separate words, translation file entries
// instead do not and can be also be quite different. If you do see an empty
// value when you expected something, check your usage, what visible fields
// in database actually constains, and maybe add a mapping from $colname to
// to the database column name to array $indexes at the beginning of this
// function. Note that inconsistencies between $colname, database column
// name, translation entry key and name in visible fields do occur sometimes
// during development phase.
if (array_key_exists($colname, $indexes)) {
$value = htmlspecialchars($task[$indexes[$colname]], ENT_QUOTES, 'utf-8');
}
break;
}
return sprintf($format, $class, $value);
}
$sort;
$orderby;
/**
*
* comparison function used by export_task_list
*
*/
function do_cmp($a, $b)
{
global $sort,$orderby;
if ($a[ $orderby ] == $b[ $orderby ]) { return 0; }
if ($sort == 'asc')
return ($a[ $orderby ] < $b[ $orderby ]) ? -1 : 1;
else
return ($a[ $orderby ] > $b[ $orderby ]) ? -1 : 1;
}
/**
* workaround fputcsv() bug https://bugs.php.net/bug.php?id=43225
*/
function my_fputcsv($handle, $fields)
{
$out = array();
foreach ($fields as $field) {
if (empty($field)) {
$out[] = '';
}
elseif (preg_match('/^\d+(\.\d+)?$/', $field)) {
$out[] = $field;
}
else {
$out[] = '"' . preg_replace('/"/', '""', $field) . '"';
}
}
return fwrite($handle, implode(',', $out) . "\n");
}
/**
* Export the tasks as a .csv file
* Currently only a fixed list of task fields
*/
function export_task_list()
{
global $tasks, $fs, $user, $sort, $orderby, $proj;
if (!is_array($tasks)){
return;
}
# TODO enforcing user permissions on allowed fields
# TODO Flyspray 1.1 or later: selected fields by user request, saved user settings, tasklist settings or project defined list which fields should appear in an export
# TODO Flyspray 1.1 or later: export in .ods open document spreadsheet, .xml ....
$indexes = array (
'id' => 'task_id',
'project' => 'project_title',
'tasktype' => 'task_type',
'category' => 'category_name',
'severity' => 'task_severity',
'priority' => 'task_priority',
'summary' => 'item_summary',
'dateopened' => 'date_opened',
'status' => 'status_name',
'openedby' => 'opened_by_name',
'assignedto' => 'assigned_to_name',
'lastedit' => 'max_date',
'reportedin' => 'product_version',
'dueversion' => 'closedby_version',
'duedate' => 'due_date',
'comments' => 'num_comments',
'votes' => 'num_votes',
'attachments'=> 'num_attachments',
'dateclosed' => 'date_closed',
'progress' => 'percent_complete',
'os' => 'os_name',
'private' => 'mark_private',
'supertask' => 'supertask_id',
'detailed_desc'=>'detailed_desc',
);
# we can put this info also in the filename ...
#$projectinfo = array('Project ', $tasks[0]['project_title'], date("H:i:s d-m-Y") );
// sort the tasks into the order selected by the user. Set
// global vars for use by sort comparison function
$sort = Get::safe('sort','desc') == 'desc' ? 'desc' : 'asc';
$field = Get::safe('order', 'id');
if ($field == '') $field = 'id';
$orderby = $indexes[ $field ];
usort($tasks, "do_cmp");
$outfile = str_replace(' ', '_', $proj->prefs['project_title']).'_'.date("Y-m-d").'.csv';
#header('Content-Type: application/csv');
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename='.$outfile);
header('Content-Transfer-Encoding: text');
header('Expires: 0');
header('Cache-Control: must-revalidate');
#header('Pragma: public');
#header('Content-Length: '.strlen($result)); # unknown at this time..
ob_clean();
flush();
$output = fopen('php://output', 'w');
$headings= array(
'ID',
'Category',
'Task Type',
'Severity',
'Summary',
'Status',
'Progress',
'date_opened',
'date_closed',
'due_date',
'supertask_id'
);
if($user->perms('view_estimated_effort') && $proj->id>0 && $proj->prefs['use_effort_tracking']){
$headings[]='Estimated Effort';
}
$headings[]='Description';
//if($user->perms('view_current_effort_done') && $proj->id>0 && $proj->prefs['use_effort_tracking']){ $headings[]='Done Effort'; }
#fputcsv($output, $headings);
my_fputcsv($output, $headings); # fixes 'SYLK' FS#2123 Excel problem
foreach ($tasks as $task) {
$row = array(
$task['task_id'],
$task['category_name'],
$task['task_type'],
$fs->severities[ $task['task_severity'] ],
$task['item_summary'],
$task['status_name'],
$task['percent_complete'],
$task['date_opened'],
$task['date_closed'],
$task['due_date'],
$task['supertask_id']
);
if( $user->perms('view_estimated_effort') && $proj->id>0 && $proj->prefs['use_effort_tracking']){
$row[]=$task['estimated_effort'];
}
$row[]=$task['detailed_desc'];
//if( $user->perms('view_current_effort_done') && $proj->id>0 && $proj->prefs['use_effort_tracking']){ $row=$task['effort']; }
my_fputcsv($output, $row); # fputcsv() is buggy
}
fclose($output);
exit();
}
// Javascript replacement
if (Get::val('toggleadvanced')) {
$advanced_search = intval(!Req::val('advancedsearch'));
Flyspray::setCookie('advancedsearch', $advanced_search, time()+60*60*24*30);
$_COOKIE['advancedsearch'] = $advanced_search;
}
/**
* Update check
*/
if(Get::has('hideupdatemsg')) {
unset($_SESSION['latest_version']);
} else if ($conf['general']['update_check']
&& $user->perms('is_admin')
&& $fs->prefs['last_update_check'] < time()-60*60*24*3) {
if (!isset($_SESSION['latest_version'])) {
$latest = Flyspray::remote_request('http://www.flyspray.org/version.txt', GET_CONTENTS);
# if for some silly reason we get an empty response, we use the actual version
$_SESSION['latest_version'] = empty($latest) ? $fs->version : $latest ;
$db->query('UPDATE {prefs} SET pref_value = ? WHERE pref_name = ?', array(time(), 'last_update_check'));
}
}
if (isset($_SESSION['latest_version']) && version_compare($fs->version, $_SESSION['latest_version'] , '<') ) {
$page->assign('updatemsg', true);
}
$page->setTitle($fs->prefs['page_title'] . $proj->prefs['project_title'] . ': ' . L('tasklist'));
$page->pushTpl('index.tpl');
?>