From 6854cb3f4d8219cf1829e32122eb2502a916eae9 Mon Sep 17 00:00:00 2001 From: Andreas Baumann Date: Sat, 1 Feb 2020 09:05:48 +0100 Subject: initial checkin --- scripts/index.php | 534 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 534 insertions(+) create mode 100644 scripts/index.php (limited to 'scripts/index.php') diff --git a/scripts/index.php b/scripts/index.php new file mode 100644 index 0000000..28e194e --- /dev/null +++ b/scripts/index.php @@ -0,0 +1,534 @@ +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 = '%s'; + $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'); + +?> -- cgit v1.2.3-70-g09d2