summaryrefslogtreecommitdiff
path: root/includes/class.tpl.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/class.tpl.php')
-rw-r--r--includes/class.tpl.php1525
1 files changed, 1525 insertions, 0 deletions
diff --git a/includes/class.tpl.php b/includes/class.tpl.php
new file mode 100644
index 0000000..24b1105
--- /dev/null
+++ b/includes/class.tpl.php
@@ -0,0 +1,1525 @@
+<?php
+
+if (!defined('IN_FS')) {
+ die('Do not access this file directly.');
+}
+
+class Tpl
+{
+ public $_uses = array();
+ public $_vars = array();
+ public $_theme = '';
+ public $_tpls = array();
+ public $_title = "";
+
+ public function uses()
+ {
+ $args = func_get_args();
+ $this->_uses = array_merge($this->_uses, $args);
+ }
+
+ public function assign($arg0 = null, $arg1 = null)
+ {
+ if (is_string($arg0)) {
+ $this->_vars[$arg0] = $arg1;
+ }elseif (is_array($arg0)) {
+ $this->_vars += $arg0;
+ }elseif (is_object($arg0)) {
+ $this->_vars += get_object_vars($arg0);
+ }
+ }
+
+ public function getTheme()
+ {
+ return $this->_theme;
+ }
+
+ public function setTheme($theme)
+ {
+ // Check available themes
+ $theme = trim($theme, '/');
+ $themes = Flyspray::listThemes();
+ if (in_array($theme, $themes)) {
+ $this->_theme = $theme.'/';
+ } else {
+ $this->_theme = $themes[0].'/';
+ }
+ }
+
+ public function setTitle($title)
+ {
+ $this->_title = $title;
+ }
+
+ public function themeUrl()
+ {
+ return sprintf('%sthemes/%s', $GLOBALS['baseurl'], $this->_theme);
+ }
+
+ public function pushTpl($_tpl)
+ {
+ $this->_tpls[] = $_tpl;
+ }
+
+ public function catch_start()
+ {
+ ob_start();
+ }
+
+ public function catch_end()
+ {
+ $this->_tpls[] = array(ob_get_contents());
+ ob_end_clean();
+ }
+
+ public function display($_tpl, $_arg0 = null, $_arg1 = null)
+ {
+ // if only plain text
+ if (is_array($_tpl) && count($tpl)) {
+ echo $_tpl[0];
+ return;
+ }
+
+ // variables part
+ if (!is_null($_arg0)) {
+ $this->assign($_arg0, $_arg1);
+ }
+
+ foreach ($this->_uses as $_var) {
+ global $$_var;
+ }
+
+ extract($this->_vars, EXTR_REFS|EXTR_SKIP);
+
+ if (is_readable(BASEDIR . '/themes/' . $this->_theme.'templates/'.$_tpl)) {
+ require BASEDIR . '/themes/' . $this->_theme.'templates/'.$_tpl;
+ } elseif (is_readable(BASEDIR . '/themes/CleanFS/templates/'.$_tpl)) {
+ # if a custom theme folder only contains a fraction of the .tpl files, use the template of the default full theme as fallback.
+ require BASEDIR . '/themes/CleanFS/templates/'.$_tpl;
+ } else {
+ # This is needed to catch times when there is no theme (for example setup pages, where BASEDIR is ../setup/ not ../)
+ require BASEDIR . "/templates/".$_tpl;
+ }
+ }
+
+ public function render()
+ {
+ while (count($this->_tpls)) {
+ $this->display(array_shift($this->_tpls));
+ }
+ }
+
+ public function fetch($tpl, $arg0 = null, $arg1 = null)
+ {
+ ob_start();
+ $this->display($tpl, $arg0, $arg1);
+ return ob_get_clean();
+ }
+}
+
+class FSTpl extends Tpl
+{
+ public $_uses = array('fs', 'conf', 'baseurl', 'language', 'proj', 'user');
+
+ public function get_image($name, $base = true)
+ {
+ global $proj, $baseurl;
+ $pathinfo = pathinfo($name);
+ $link = sprintf('themes/%s/', $proj->prefs['theme_style']);
+ if ($pathinfo['dirname'] != '.') {
+ $link .= $pathinfo['dirname'] . '/';
+ $name = $pathinfo['basename'];
+ }
+
+ $extensions = array('.png', '.gif', '.jpg', '.ico');
+
+ foreach ($extensions as $ext) {
+ if (is_file(BASEDIR . '/' . $link . $name . $ext)) {
+ return ($base) ? ($baseurl . $link . $name . $ext) : ($link . $name . $ext);
+ }
+ }
+ return '';
+ }
+
+}
+
+/**
+ * Draws the form start tag and the important anticsrftoken on 'post'-forms
+ *
+ * @param string action
+ * @param string name optional attribute of form tag
+ * @param string method optional request method, default 'post'
+ * @param string enctype optional enctype, default 'multipart/form-data'
+ * @param string attr optional attributes for the form tag, example: 'id="myformid" class="myextracssclass"'
+ *
+ * @return string
+ */
+function tpl_form($action, $name=null, $method=null, $enctype=null, $attr='')
+{
+ global $baseurl;
+
+ if (null === $method) {
+ $method='post';
+ }
+ if (null === $enctype) {
+ $enctype='multipart/form-data';
+ }
+
+ if(substr($action,0,4)!='http'){$action=$baseurl.$action;}
+ return '<form action="'.$action.'"'.($method=='get'?' method="get"':' method="post"').
+ ( $name!='' ? ' name="'.$name.'"':'').
+ ( ' enctype="'.$enctype.'"').
+ ( ' '.$attr).'>'.
+ ( $method=='post' ? '<input type="hidden" name="csrftoken" value="'.$_SESSION['csrftoken'].'" />':'');
+}
+
+/**
+ * Creates a link to a task
+ *
+ * @param array task with properties of a task. It also accepts a task_id, but that requires extra queries executed by this function.
+ * @param string text optional, by default the FS# + summary of task is used.
+ * @param bool strict check task permissions by the function too. Extra SQL queries if set true. default false.
+ * @param array attr extra attributes
+ * @param array title informations shown when hover over the link (title attribute of the HTML a-tag)
+ *
+ * @return string ready for html output
+ */
+function tpl_tasklink($task, $text = null, $strict = false, $attrs = array(), $title = array('status','summary','percent_complete'))
+{
+ global $user;
+
+ $params = array();
+
+ if (!is_array($task) || !isset($task['status_name'])) {
+ $td_id = (is_array($task) && isset($task['task_id'])) ? $task['task_id'] : $task;
+ $task = Flyspray::getTaskDetails($td_id, true);
+ }
+
+ if ($strict === true && (!is_object($user) || !$user->can_view_task($task))) {
+ return '';
+ }
+
+ if (is_object($user) && $user->can_view_task($task)) {
+ $summary = utf8_substr($task['item_summary'], 0, 64);
+ } else {
+ $summary = L('taskmadeprivate');
+ }
+
+ if (is_null($text)) {
+ $text = sprintf('FS#%d - %s', $task['task_id'], Filters::noXSS($summary));
+ } elseif(is_string($text)) {
+ $text = htmlspecialchars(utf8_substr($text, 0, 64), ENT_QUOTES, 'utf-8');
+ } else {
+ //we can't handle non-string stuff here.
+ return '';
+ }
+
+ if (!$task['task_id']) {
+ return $text;
+ }
+
+ $title_text = array();
+
+ foreach($title as $info)
+ {
+ switch($info)
+ {
+ case 'status':
+ if ($task['is_closed']) {
+ $title_text[] = $task['resolution_name'];
+ $attrs['class'] = 'closedtasklink';
+ } else {
+ $title_text[] = $task['status_name'];
+ }
+ break;
+
+ case 'summary':
+ $title_text[] = $summary;
+ break;
+
+ case 'assignedto':
+ if (isset($task['assigned_to_name']) ) {
+ if (is_array($task['assigned_to_name'])) {
+ $title_text[] = implode(', ', $task['assigned_to_name']);
+ } else {
+ $title_text[] = $task['assigned_to_name'];
+ }
+ }
+ break;
+
+ case 'percent_complete':
+ $title_text[] = $task['percent_complete'].'%';
+ break;
+
+ case 'category':
+ if ($task['product_category']) {
+ if (!isset($task['category_name'])) {
+ $task = Flyspray::getTaskDetails($task['task_id'], true);
+ }
+ $title_text[] = $task['category_name'];
+ }
+ break;
+
+ // ... more options if necessary
+ }
+ }
+
+ $title_text = implode(' | ', $title_text);
+
+ // to store search options
+ $params = $_GET;
+ unset($params['do'], $params['action'], $params['task_id'], $params['switch']);
+ if(isset($params['event_number'])){
+ # shorter links to tasks from report page
+ unset($params['events'], $params['event_number'], $params['fromdate'], $params['todate'], $params['submit']);
+ }
+
+ # We can unset the project param for shorter urls because flyspray knows project_id from current task data.
+ # Except we made a search from an 'all projects' view before, so the prev/next navigation on details page knows
+ # if it must search only in the project of current task or all projects the user is allowed to see tasks.
+ if(!isset($params['advancedsearch']) || (isset($params['project']) && $params['project']!=0) ){
+ unset($params['project']);
+ }
+
+ $url = htmlspecialchars(createURL('details', $task['task_id'], null, $params), ENT_QUOTES, 'utf-8');
+ $title_text = htmlspecialchars($title_text, ENT_QUOTES, 'utf-8');
+ $link = sprintf('<a href="%s" title="%s" %s>%s</a>',$url, $title_text, join_attrs($attrs), $text);
+
+ if ($task['is_closed']) {
+ $link = '<del>&#160;' . $link . '&#160;</del>';
+ }
+ return $link;
+}
+
+/*
+ * Creates a textlink to a user profile.
+ *
+ * For a link with user icon use tpl_userlinkavatar().
+ *
+ * @param int uid user_id from {users} db table
+ */
+function tpl_userlink($uid)
+{
+ global $db, $user;
+
+ static $cache = array();
+
+ if (is_array($uid)) {
+ list($uid, $uname, $rname) = $uid;
+ } elseif (empty($cache[$uid])) {
+ $sql = $db->query('SELECT user_name, real_name FROM {users} WHERE user_id = ?',
+ array(intval($uid)));
+ if ($sql && $db->countRows($sql)) {
+ list($uname, $rname) = $db->fetchRow($sql);
+ }
+ }
+
+ if (isset($uname)) {
+ #$url = createURL(($user->perms('is_admin')) ? 'edituser' : 'user', $uid);
+ # peterdd: I think it is better just to link to the user's page instead direct to the 'edit user' page also for admins.
+ # With more personalisation coming (personal todo list, charts, ..) in future to flyspray
+ # the user page itself is of increasing value. Instead show the 'edit user'-button on user's page.
+ $url = createURL('user', $uid);
+ $cache[$uid] = vsprintf('<a href="%s">%s</a>', array_map(array('Filters', 'noXSS'), array($url, $rname)));
+ } elseif (empty($cache[$uid])) {
+ $cache[$uid] = eL('anonymous');
+ }
+
+ return $cache[$uid];
+}
+
+/**
+* Builds the HTML string for displaying a gravatar image or an uploaded user image.
+* The string for a user and a size is cached per request.
+*
+* Class and style parameter should be avoided to make this function more effective for caching (less SQL queries)
+*
+* @param int uid the id of the user
+* @param int size in pixel for displaying. Should use global max_avatar_size pref setting by default.
+* @param string class optional, avoid calling with class parameter for better 'cacheability'
+* @param string style optional, avoid calling with style parameter for better 'cacheability'
+*/
+function tpl_userlinkavatar($uid, $size, $class='', $style='')
+{
+ global $db, $user, $baseurl, $fs;
+
+ static $avacache=array();
+
+ if( !($uid>0) ){
+ return '<i class="fa fa-user"></i>';
+ }
+
+ if($uid>0 && (empty($avacache[$uid]) || !isset($avacache[$uid][$size]))){
+ if (!isset($avacache[$uid]['uname'])) {
+ $sql = $db->query('SELECT user_name, real_name, email_address, profile_image FROM {users} WHERE user_id = ?', array(intval($uid)));
+ if ($sql && $db->countRows($sql)) {
+ list($uname, $rname, $email, $profile_image) = $db->fetchRow($sql);
+ } else {
+ return;
+ }
+ $avacache[$uid]['profile_image'] = $profile_image;
+ $avacache[$uid]['uname'] = $uname;
+ $avacache[$uid]['rname'] = $rname;
+ $avacache[$uid]['email'] = $email;
+ }
+
+ if (is_file(BASEDIR.'/avatars/'.$avacache[$uid]['profile_image'])) {
+ $image = '<img src="'.$baseurl.'avatars/'.$avacache[$uid]['profile_image'].'"/>';
+ } else {
+ if (isset($fs->prefs['gravatars']) && $fs->prefs['gravatars'] == 1) {
+ $email = md5(strtolower(trim($avacache[$uid]['email'])));
+ $default = 'mm';
+ $imgurl = '//www.gravatar.com/avatar/'.$email.'?d='.urlencode($default).'&s='.$size;
+ $image = '<img src="'.$imgurl.'"/>';
+ } else {
+ $image = '<i class="fa fa-user" style="font-size:'.$size.'px"></i>';
+ }
+ }
+ if (isset($avacache[$uid]['uname'])) {
+ #$url = createURL(($user->perms('is_admin')) ? 'edituser' : 'user', $uid);
+ # peterdd: I think it is better just to link to the user's page instead direct to the 'edit user' page also for admins.
+ # With more personalisation coming (personal todo list, charts, ..) in future to flyspray
+ # the user page itself is of increasing value. Instead show the 'edit user'-button on user's page.
+ $url = createURL('user', $uid);
+ $avacache[$uid][$size] = '<a'.($class!='' ? ' class="'.$class.'"':'').($style!='' ? ' style="'.$style.'"':'').' href="'.$url.'" title="'.Filters::noXSS($avacache[$uid]['rname']).'">'.$image.'</a>';
+ }
+ }
+ return $avacache[$uid][$size];
+}
+
+function tpl_fast_tasklink($arr)
+{
+ return tpl_tasklink($arr[1], $arr[0]);
+}
+
+/**
+ * Formats a task tag for HTML output based on a global $alltags array
+ *
+ * @param int id tag_id of {list_tag} db table
+ * @param bool showid set true if the tag_id is shown instead of the tag_name
+ *
+ * @return string ready for output
+ */
+function tpl_tag($id, $showid=false) {
+ global $alltags;
+
+ if(!is_array($alltags)) {
+ $alltags=Flyspray::getAllTags();
+ }
+
+ if(isset($alltags[$id])){
+ $out='<i class="tag t'.$id;
+ if( isset($alltags[$id]['class']) && preg_match('/^#([0-9a-f]{3}){1,2}$/i', $alltags[$id]['class']) ) {
+ $out.= '" style="background-color:'.$alltags[$id]['class'];
+ # max only calc once per tag per request
+ # assumes theme css of default tag font color is #000
+ if(!isset($alltags[$id]['fcolor'])){
+ $bg = hex2RGB($alltags[$id]['class']);
+ # from https://www.w3.org/TR/AERT/#color-contrast
+ $brightness=(299*$bg['r'] + 587*$bg['g'] + 114*$bg['b']) / 1000;
+ if($brightness<126){
+ $out.=';color:#fff';
+ $alltags[$id]['fcolor']='#fff';
+ }else{
+ $alltags[$id]['fcolor']='';
+ }
+ } else if( $alltags[$id]['fcolor']==='#fff'){
+ $out.=';color:#fff';
+ }
+ $out.='"';
+ } else {
+ $out.= (isset($alltags[$id]['class']) ? ' '.htmlspecialchars($alltags[$id]['class'], ENT_QUOTES, 'utf-8') : '').'"';
+ }
+ if($showid){
+ $out.='>'.$id;
+ } else{
+ $out.=' title="'.htmlspecialchars($alltags[$id]['tag_name'], ENT_QUOTES, 'utf-8').'">';
+ }
+
+ $out.='</i>';
+ return $out;
+ }
+}
+
+/**
+* Convert a hexa decimal color code to its RGB equivalent
+*
+* used by tpl_tag()
+*
+* @param string $hexstr (hexadecimal color value)
+* @param boolean $returnasstring (if set true, returns the value separated by the separator character. Otherwise returns associative array)
+* @param string $seperator (to separate RGB values. Applicable only if second parameter is true.)
+* @return array or string (depending on second parameter. Returns False if invalid hex color value)
+*
+* function is adapted from an exmaple on http://php.net/manual/de/function.hexdec.php
+*/
+function hex2RGB($hexstr, $returnasstring = false, $seperator = ',') {
+ $hexstr = preg_replace("/[^0-9A-Fa-f]/", '', $hexstr); // Gets a proper hex string
+ $rgb = array();
+ if (strlen($hexstr) == 6) { // if a proper hex code, convert using bitwise operation. No overhead... faster
+ $colorval = hexdec($hexstr);
+ $rgb['r'] = 0xFF & ($colorval >> 0x10);
+ $rgb['g'] = 0xFF & ($colorval >> 0x8);
+ $rgb['b'] = 0xFF & $colorval;
+ } elseif (strlen($hexstr) == 3) { // if shorthand notation, need some string manipulations
+ $rgb['r'] = hexdec(str_repeat(substr($hexstr, 0, 1), 2));
+ $rgb['g'] = hexdec(str_repeat(substr($hexstr, 1, 1), 2));
+ $rgb['b'] = hexdec(str_repeat(substr($hexstr, 2, 1), 2));
+ } else {
+ return false; // invalid hex color code
+ }
+ return $returnasstring ? implode($seperator, $rgb) : $rgb; // returns the rgb string or the associative array
+}
+
+/**
+ * joins an array of tag attributes together for output in a HTML tag.
+ *
+ * @param array attr
+ *
+ * @return string
+ */
+function join_attrs($attr = null) {
+ if (is_array($attr) && count($attr)) {
+ $arr = array();
+ foreach ($attr as $key=>$val) {
+ $arr[] = vsprintf('%s = "%s"', array_map(array('Filters', 'noXSS'), array($key, $val)));
+ }
+ return ' '.join(' ', $arr);
+ }
+ return '';
+}
+
+/**
+ * Datepicker
+ */
+function tpl_datepicker($name, $label = '', $value = 0) {
+ global $user, $page;
+
+ $date = '';
+
+ if ($value) {
+ if (!is_numeric($value)) {
+ $value = strtotime($value);
+ }
+
+ if (!$user->isAnon()) {
+ $st = date('Z')/3600; // server GMT timezone
+ $value += ($user->infos['time_zone'] - $st) * 60 * 60;
+ }
+
+ $date = date('Y-m-d', intval($value));
+
+ /* It must "look" as a date..
+ * XXX : do not blindly copy this code to validate other dates
+ * this is mostly a tongue-in-cheek validation
+ * 1. it will fail on 32 bit systems on dates < 1970
+ * 2. it will produce different results bewteen 32 and 64 bit systems for years < 1970
+ * 3. it will not work when year > 2038 on 32 bit systems (see http://en.wikipedia.org/wiki/Year_2038_problem)
+ *
+ * Fortunately tasks are never opened to be dated on 1970 and maybe our sons or the future flyspray
+ * coders may be willing to fix the 2038 issue ( in the strange case 32 bit systems are still used by that year) :-)
+ */
+
+ } elseif (Req::has($name) && strlen(Req::val($name))) {
+
+ //strtotime sadly returns -1 on faliure in php < 5.1 instead of false
+ $ts = strtotime(Req::val($name));
+
+ foreach (array('m','d','Y') as $period) {
+ //checkdate only accepts arguments of type integer
+ $$period = intval(date($period, $ts));
+ }
+ // $ts has to be > 0 to get around php behavior change
+ // false is casted to 0 by the ZE
+ $date = ($ts > 0 && checkdate($m, $d, $Y)) ? Req::val($name) : '';
+ }
+
+
+ $subPage = new FSTpl;
+ $subPage->setTheme($page->getTheme());
+ $subPage->assign('name', $name);
+ $subPage->assign('date', $date);
+ $subPage->assign('label', $label);
+ $subPage->assign('dateformat', '%Y-%m-%d');
+ $subPage->display('common.datepicker.tpl');
+}
+
+/**
+ * user selector
+ */
+function tpl_userselect($name, $value = null, $id = '', $attrs = array()) {
+ global $db, $user, $proj;
+
+ if (!$id) {
+ $id = $name;
+ }
+
+ if ($value && ctype_digit($value)) {
+ $sql = $db->query('SELECT user_name FROM {users} WHERE user_id = ?', array($value));
+ $value = $db->fetchOne($sql);
+ }
+
+ if (!$value) {
+ $value = '';
+ }
+
+
+ $page = new FSTpl;
+ $page->setTheme($proj->prefs['theme_style']);
+ $page->assign('name', $name);
+ $page->assign('id', $id);
+ $page->assign('value', $value);
+ $page->assign('attrs', $attrs);
+ $page->display('common.userselect.tpl');
+}
+
+/**
+ * Creates the options for a date format select
+ *
+ * @selected The format that should by selected by default
+ * @return html formatted options for a select tag
+**/
+function tpl_date_formats($selected, $detailed = false)
+{
+ $time = time();
+
+ # TODO: rewrite using 'return tpl_select(...)'
+ if (!$detailed) {
+ $dateFormats = array(
+ '%d.%m.%Y' => strftime('%d.%m.%Y', $time).' (DD.MM.YYYY)', # popular in many european countries
+ '%d/%m/%Y' => strftime('%d/%m/%Y', $time).' (DD/MM/YYYY)', # popular in Greek
+ '%m/%d/%Y' => strftime('%m/%d/%Y', $time).' (MM/DD/YYYY)', # popular in USA
+
+ '%d.%m.%y' => strftime('%d.%m.%y', $time),
+
+ '%Y.%m.%d' => strftime('%Y.%m.%d', $time),
+ '%y.%m.%d' => strftime('%y.%m.%d', $time),
+
+ '%d-%m-%Y' => strftime('%d-%m-%Y', $time),
+ '%d-%m-%y' => strftime('%d-%m-%y', $time),
+
+ '%Y-%m-%d' => strftime('%Y-%m-%d', $time).' (YYYY-MM-DD, ISO 8601)',
+ '%y-%m-%d' => strftime('%y-%m-%d', $time),
+
+ '%d %b %Y' => strftime('%d %b %Y', $time),
+ '%d %B %Y' => strftime('%d %B %Y', $time),
+
+ '%b %d %Y' => strftime('%b %d %Y', $time),
+ '%B %d %Y' => strftime('%B %d %Y', $time),
+ );
+ }
+ else {
+ # TODO: maybe use optgroups for tpl_select() to separate 24h and 12h (am/pm) formats
+ $dateFormats = array(
+ '%d.%m.%Y %H:%M' => strftime('%d.%m.%Y %H:%M', $time),
+ '%d.%m.%y %H:%M' => strftime('%d.%m.%y %H:%M', $time),
+
+ '%d.%m.%Y %I:%M %p' => strftime('%d.%m.%Y %I:%M %p', $time),
+ '%d.%m.%y %I:%M %p' => strftime('%d.%m.%y %I:%M %p', $time),
+
+ '%Y.%m.%d %H:%M' => strftime('%Y.%m.%d %H:%M', $time),
+ '%y.%m.%d %H:%M' => strftime('%y.%m.%d %H:%M', $time),
+
+ '%Y.%m.%d %I:%M %p' => strftime('%Y.%m.%d %I:%M %p', $time),
+ '%y.%m.%d %I:%M %p' => strftime('%y.%m.%d %I:%M %p', $time),
+
+ '%d-%m-%Y %H:%M' => strftime('%d-%m-%Y %H:%M', $time),
+ '%d-%m-%y %H:%M' => strftime('%d-%m-%y %H:%M', $time),
+
+ '%d-%m-%Y %I:%M %p' => strftime('%d-%m-%Y %I:%M %p', $time),
+ '%d-%m-%y %I:%M %p' => strftime('%d-%m-%y %I:%M %p', $time),
+
+ '%Y-%m-%d %H:%M' => strftime('%Y-%m-%d %H:%M', $time),
+ '%y-%m-%d %H:%M' => strftime('%y-%m-%d %H:%M', $time),
+
+ '%Y-%m-%d %I:%M %p' => strftime('%Y-%m-%d %I:%M %p', $time),
+ '%y-%m-%d %I:%M %p' => strftime('%y-%m-%d %I:%M %p', $time),
+
+ '%d %b %Y %H:%M' => strftime('%d %b %Y %H:%M', $time),
+ '%d %B %Y %H:%M' => strftime('%d %B %Y %H:%M', $time),
+
+ '%d %b %Y %I:%M %p' => strftime('%d %b %Y %I:%M %p', $time),
+ '%d %B %Y %I:%M %p' => strftime('%d %B %Y %I:%M %p', $time),
+
+ '%b %d %Y %H:%M' => strftime('%b %d %Y %H:%M', $time),
+ '%B %d %Y %H:%M' => strftime('%B %d %Y %H:%M', $time),
+
+ '%b %d %Y %I:%M %p' => strftime('%b %d %Y %I:%M %p', $time),
+ '%B %d %Y %I:%M %p' => strftime('%B %d %Y %I:%M %p', $time),
+ );
+ }
+
+ return tpl_options($dateFormats, $selected);
+}
+
+
+/**
+ * Options for a <select>
+ *
+ * FIXME peterdd: This function is currently often called by templates with just
+ * results from sqltablequeries like select * from tablex,
+ * so data[0] and data[1] of each row works only by table structure convention as wished.
+ * not by names, lack of a generic optgroup feature, css-id, css-classes, disabled option.
+ * Maybe rewrite a as tpl_select() ..
+ *
+ * @options array of values
+ * For optgroups, the values should be presorted by the optgroups
+ * example:
+ * $options=array(
+ * array(3,'project3',1), # active project group
+ * array(2,'project2',1),
+ * array(5,'project5',0) # inactive project optgroup
+ * ); tpl_options($options, 2)
+*/
+function tpl_options($options, $selected = null, $labelIsValue = false, $attr = null, $remove = null)
+{
+ $html = '';
+
+ // force $selected to be an array.
+ // this allows multi-selects to have multiple selected options.
+ $selected = is_array($selected) ? $selected : (array) $selected;
+ $options = is_array($options) ? $options : (array) $options;
+
+ $lastoptgroup=0;
+ $optgroup=0;
+ $ingroup=false;
+ foreach ($options as $idx=>$data) {
+ if (is_array($data)) {
+ $value = $data[0];
+ $label = $data[1];
+ # just a temp hack, we currently use optgroups only for project dropdown...
+ $optgroup=array_key_exists('project_is_active',$data) ? $data['project_is_active'] : 0;
+ if (array_key_exists('project_title', $data) && $optgroup!=$lastoptgroup) {
+ if ($ingroup) {
+ $html.='</optgroup>';
+ }
+ $html.='<optgroup'.($optgroup==0 ? ' label="'.L('inactive').'"' : '' ).'>';
+ $ingroup=true;
+ }
+ } else{
+ $value=$idx;
+ $label=$data;
+ }
+ $label = htmlspecialchars($label, ENT_QUOTES, 'utf-8');
+ $value = $labelIsValue ? $label : htmlspecialchars($value, ENT_QUOTES, 'utf-8');
+
+ if ($value === $remove) {
+ continue;
+ }
+
+ $html .= '<option value="'.$value.'"';
+ if (in_array($value, $selected)) {
+ $html .= ' selected="selected"';
+ }
+ $html .= ($attr ? join_attrs($attr): '') . '>' . $label . '</option>';
+ $lastoptgroup=$optgroup;
+ }
+
+ if ($ingroup) {
+ $html.='</optgroup>';
+ }
+
+ if (!$html) {
+ $html .= '<option value="0">---</option>';
+ }
+
+ return $html;
+}
+
+
+/**
+ * Builds a complete HTML-select with select options.
+ *
+ * Supports free choosable attributes for select, options and optgroup tags. optgroups can also be nested.
+ *
+ * @author peterdd
+ *
+ * @param array key-values pairs and can be nested
+ *
+ * @return string the complete html-select
+ *
+ * @since 1.0.0-beta3
+ *
+ * @example
+ * example output of print_r($array) to see the structure of the param $array
+ * Array
+ (
+ [name] => varname // required if you want submit it with a form to the server
+ [attr] => Array // optional
+ (
+ [id] => selid // optional
+ [class] => selclass1 // optional
+ )
+ [options] => Array // optional, but without doesn't make much sense ;-)
+ (
+ [0] => Array // at least one would be useful
+ (
+ [value] => opt1val // recommended
+ [label] => opt1label // recommended
+ [disabled] => 1 // optional
+ [selected] => 1 // optional
+ [attr] => Array // optional
+ (
+ [id] => opt1id // optional
+ [class] => optclass1 // optional
+ )
+ )
+ [1] => Array
+ (
+ [optgroup] => 1 // this tells the function that now comes an optgroup
+ [label] => optgrouplabel // optional
+ [attr] => Array // optional
+ (
+ [id] => optgroupid1 // optional
+ [class] => optgroupclass1 // optional
+ )
+ [options] => Array
+ // ... nested options and optgroups can follow here....
+ )
+ // ... and so on
+ )
+ )
+ */
+function tpl_select($select=array()){
+
+ if(isset($select['name'])){
+ $name=' name="'.$select['name'].'"';
+ }else{
+ $name='';
+ }
+
+ $attrjoin='';
+ if(isset($select['attr'])){
+ foreach($select['attr'] as $key=>$val){
+ $attrjoin.=' '.$key.'="'.htmlspecialchars($val, ENT_QUOTES, 'utf-8').'"';
+ }
+ }
+ $html='<select'.$name.$attrjoin.'>';
+ if(isset($select['options'])){
+ $html.=tpl_selectoptions($select['options']);
+ }
+ $html.="\n".'</select>';
+ return $html;
+}
+
+/**
+ * called by tpl_select()
+ *
+ * @author peterdd
+ *
+ * @param array key-values pairs and can be nested
+ *
+ * @return string option- and optgroup-tags as one string
+ *
+ * @since 1.0.0-beta3
+ *
+ * called recursively by itself
+ * Can also be called alone from template if the templates writes the wrapping select-tags.
+ *
+ * @example see [options]-array of example of tpl_select()
+ */
+function tpl_selectoptions($options=array(), $level=0){
+ $html='';
+ # such deep nesting is too weired - probably an endless loop lets
+ # return before something bad happens
+ if( $level>10){
+ return;
+ }
+ #print_r($options);
+ #print_r($level);
+ foreach($options as $o){
+ if(isset($o['optgroup'])){
+ # we have an optgroup
+ $html.="\n".str_repeat("\t",$level).'<optgroup label="'.$o['label'].'"';
+ if(isset($o['attr'])){
+ foreach($o['attr'] as $key=>$val){
+ $html.=' '.$key.'="'.htmlspecialchars($val, ENT_QUOTES, 'utf-8').'"';
+ }
+ }
+ $html.='>';
+ # may contain options and suboptgroups..
+ $html.=tpl_selectoptions($o['options'], $level+1);
+ $html.="\n".str_repeat("\t",$level).'</optgroup>';
+ } else{
+ # we have a simple option
+ $html.="\n".str_repeat("\t",$level).'<option value="'.htmlspecialchars($o['value'], ENT_QUOTES, 'utf-8').'"';
+ if(isset($o['disabled'])){
+ $html.=' disabled="disabled"'; # xhtml compatible
+ }
+ if(isset($o['selected'])){
+ $html.=' selected="selected"'; # xhtml compatible
+ }
+ if(isset($o['attr'])){
+ foreach($o['attr'] as $key=>$val){
+ $html.=' '.$key.'="'.htmlspecialchars($val, ENT_QUOTES, 'utf-8').'"';
+ }
+ }
+ $html.='>'.htmlspecialchars($o['label'], ENT_QUOTES, 'utf-8').'</option>';
+ }
+ }
+
+ return $html;
+}
+
+
+/**
+ * Creates a double select.
+ *
+ * Elements of arrays $options and $selected can be moved between eachother. The $selected list can also be sorted.
+ *
+ * @param string name
+ * @param array options
+ * @param array selected
+ * @param bool labelisvalue
+ * @param bool updown
+ */
+function tpl_double_select($name, $options, $selected = null, $labelisvalue = false, $updown = true)
+{
+ static $_id = 0;
+ static $tpl = null;
+
+ if (!$tpl) {
+ global $proj;
+
+ // poor man's cache
+ $tpl = new FSTpl();
+ $tpl->setTheme($proj->prefs['theme_style']);
+ }
+
+ settype($selected, 'array');
+ settype($options, 'array');
+
+ $tpl->assign('id', '_task_id_'.($_id++));
+ $tpl->assign('name', $name);
+ $tpl->assign('selected', $selected);
+ $tpl->assign('updown', $updown);
+
+ $html = $tpl->fetch('common.dualselect.tpl');
+
+ $selectedones = array();
+
+ $opt1 = '';
+ foreach ($options as $value => $label) {
+ if (is_array($label) && count($label) >= 2) {
+ $value = $label[0];
+ $label = $label[1];
+ }
+ if ($labelisvalue) {
+ $value = $label;
+ }
+ if (in_array($value, $selected)) {
+ $selectedones[$value] = $label;
+ continue;
+ }
+ $label = htmlspecialchars($label, ENT_QUOTES, 'utf-8');
+ $value = htmlspecialchars($value, ENT_QUOTES, 'utf-8');
+
+ $opt1 .= sprintf('<option title="%2$s" value="%1$s">%2$s</option>', $value, $label);
+ }
+
+ $opt2 = '';
+ foreach ($selected as $value) {
+ if (!isset($selectedones[$value])) {
+ continue;
+ }
+ $label = htmlspecialchars($selectedones[$value], ENT_QUOTES, 'utf-8');
+ $value = htmlspecialchars($value, ENT_QUOTES, 'utf-8');
+
+ $opt2 .= sprintf('<option title="%2$s" value="%1$s">%2$s</option>', $value, $label);
+ }
+
+ return sprintf($html, $opt1, $opt2);
+}
+
+/**
+ * Creates a HTML checkbox
+ *
+ * @param string name
+ * @param bool checked
+ * @param string id id attribute of the checkbox HTML element
+ * @param string value
+ * @param array attr tag attributes
+ *
+ * @return string for ready for HTML output
+ */
+function tpl_checkbox($name, $checked = false, $id = null, $value = 1, $attr = null)
+{
+ $name = htmlspecialchars($name, ENT_QUOTES, 'utf-8');
+ $value = htmlspecialchars($value, ENT_QUOTES, 'utf-8');
+ $html = sprintf('<input type="checkbox" name="%s" value="%s" ', $name, $value);
+ if (is_string($id)) {
+ $html .= sprintf('id="%s" ', Filters::noXSS($id));
+ }
+ if ($checked == true) {
+ $html .= 'checked="checked" ';
+ }
+ // do not call join_attrs if $attr is null or nothing..
+ return ($attr ? $html. join_attrs($attr) : $html) . '/>';
+}
+
+/**
+ * Image display
+ */
+function tpl_img($src, $alt = '')
+{
+ global $baseurl;
+ if (is_file(BASEDIR .'/'.$src)) {
+ return sprintf('<img src="%s%s" alt="%s" />', $baseurl, Filters::noXSS($src), Filters::noXSS($alt));
+ }
+ return Filters::noXSS($alt);
+}
+
+// Text formatting
+//format has been already checked in constants.inc.php
+if(isset($conf['general']['syntax_plugin'])) {
+
+ $path_to_plugin = BASEDIR . '/plugins/' . $conf['general']['syntax_plugin'] . '/' . $conf['general']['syntax_plugin'] . '_formattext.inc.php';
+
+ if (is_readable($path_to_plugin)) {
+ include($path_to_plugin);
+ }
+}
+
+class TextFormatter
+{
+ public static function get_javascript()
+ {
+ global $conf;
+
+ $path_to_plugin = sprintf('%s/plugins/%s', BASEDIR, $conf['general']['syntax_plugin']);
+ $return = array();
+
+ if (!is_readable($path_to_plugin)) {
+ return $return;
+ }
+
+ $d = dir($path_to_plugin);
+ while (false !== ($entry = $d->read())) {
+ if (substr($entry, -3) == '.js') {
+ $return[] = $conf['general']['syntax_plugin'] . '/' . $entry;
+ }
+ }
+
+ return $return;
+ }
+
+ public static function render($text, $type = null, $id = null, $instructions = null)
+ {
+ global $conf;
+
+ $methods = get_class_methods($conf['general']['syntax_plugin'] . '_TextFormatter');
+ $methods = is_array($methods) ? $methods : array();
+
+ if (in_array('render', $methods)) {
+ return call_user_func(array($conf['general']['syntax_plugin'] . '_TextFormatter', 'render'),
+ $text, $type, $id, $instructions);
+ } else {
+ $text=strip_tags($text, '<br><br/><p><h2><h3><h4><h5><h5><h6><blockquote><a><img><u><b><strong><s><ins><del><ul><ol><li><table><caption><tr><col><colgroup><td><th><thead><tfoot><tbody><code>');
+ if ( $conf['general']['syntax_plugin']
+ && $conf['general']['syntax_plugin'] != 'none'
+ && $conf['general']['syntax_plugin'] != 'html') {
+ $text='Unsupported output plugin '.$conf['general']['syntax_plugin'].'!'
+ .'<br/>Couldn\'t call '.$conf['general']['syntax_plugin'].'_TextFormatter::render()'
+ .'<br/>Temporarily handled like it is HTML until fixed.<br/>'
+ .$text;
+ }
+
+ //TODO: Remove Redundant Code once tested completely
+ //Author: Steve Tredinnick
+ //Have removed this as creating additional </br> lines even though <p> is already dealing with it
+ //possibly an conversion from Dokuwiki syntax to html issue, left in in case anyone has issues and needs to comment out
+ //$text = ' ' . nl2br($text) . ' ';
+
+ // Change FS#123 into hyperlinks to tasks
+ return preg_replace_callback("/\b(?:FS#|bug )(\d+)\b/", 'tpl_fast_tasklink', trim($text));
+ }
+ }
+
+ public static function textarea($name, $rows, $cols, $attrs = null, $content = null)
+ {
+ global $conf;
+
+ if (@in_array('textarea', get_class_methods($conf['general']['syntax_plugin'] . '_TextFormatter'))) {
+ return call_user_func(array($conf['general']['syntax_plugin'] . '_TextFormatter', 'textarea'),
+ $name, $rows, $cols, $attrs, $content);
+ }
+
+ $name = htmlspecialchars($name, ENT_QUOTES, 'utf-8');
+ $return = sprintf('<textarea name="%s" cols="%d" rows="%d"', $name, $cols, $rows);
+ if (is_array($attrs) && count($attrs)) {
+ $return .= join_attrs($attrs);
+ }
+ $return .= '>';
+ if (is_string($content) && strlen($content)) {
+ $return .= htmlspecialchars($content, ENT_QUOTES, 'utf-8');
+ }
+ $return .= '</textarea>';
+
+ # Activate CkEditor on textareas
+ if($conf['general']['syntax_plugin']=='html'){
+ $return .= "
+<script>
+ CKEDITOR.replace( '".$name."', { entities: true, entities_latin: false, entities_processNumerical: false } );
+</script>";
+ }
+
+ return $return;
+ }
+}
+
+/**
+ * Format Date
+ *
+ * Questionable if this function belongs in this class. Usages also elsewhere and not UI-related.
+ */
+function formatDate($timestamp, $extended = false, $default = '')
+{
+ global $db, $conf, $user, $fs;
+
+ setlocale(LC_ALL, str_replace('-', '_', L('locale')) . '.utf8');
+
+ if (!$timestamp) {
+ return $default;
+ }
+
+ $dateformat = '';
+ $format_id = $extended ? 'dateformat_extended' : 'dateformat';
+ $st = date('Z')/3600; // server GMT timezone
+
+ if (!$user->isAnon()) {
+ $dateformat = $user->infos[$format_id];
+ $timestamp += ($user->infos['time_zone'] - $st) * 60 * 60;
+ $st = $user->infos['time_zone'];
+ }
+
+ if (!$dateformat) {
+ $dateformat = $fs->prefs[$format_id];
+ }
+
+ if (!$dateformat) {
+ $dateformat = $extended ? '%A, %d %B %Y, %H:%M %GMT' : '%Y-%m-%d';
+ }
+
+ $zone = L('GMT') . (($st == 0) ? ' ' : (($st > 0) ? '+' . $st : $st));
+ $dateformat = str_replace('%GMT', $zone, $dateformat);
+ //it returned utf-8 encoded by the system
+ return strftime(Filters::noXSS($dateformat), (int) $timestamp);
+}
+
+/**
+ * Draw permissions table
+ */
+function tpl_draw_perms($perms)
+{
+ global $proj;
+
+ $perm_fields = array(
+ 'is_admin',
+ 'manage_project',
+ 'view_tasks',
+ 'view_groups_tasks',
+ 'view_own_tasks',
+ 'open_new_tasks',
+ 'add_multiple_tasks',
+ 'modify_own_tasks',
+ 'modify_all_tasks',
+ 'create_attachments',
+ 'delete_attachments',
+ 'assign_to_self',
+ 'assign_others_to_self',
+ 'edit_assignments',
+ 'close_own_tasks',
+ 'close_other_tasks',
+ 'view_roadmap',
+ 'view_history',
+ 'view_reports',
+ 'add_votes',
+ 'view_comments',
+ 'add_comments',
+ 'edit_comments',
+ 'edit_own_comments',
+ 'delete_comments',
+ 'view_estimated_effort',
+ 'view_current_effort_done',
+ 'track_effort'
+ );
+
+ $yesno = array(
+ '<td class="bad fa fa-ban" title="'.eL('no').'"></td>',
+ '<td class="good fa fa-check" title="'.eL('yes').'"></td>'
+ );
+
+ # 20150307 peterdd: This a temporary hack
+ $i=0;
+ $html='';
+ $projpermnames='';
+
+ foreach ($perms as $projperm){
+ $html .= '<table class="perms"><thead><tr><th>'.($i==0? 'global' : L('project').' '.$i).'</th>'.($i==0? '<th>'.L('permissions').'</th>' : '').'</tr></thead><tbody>';
+ foreach ($projperm as $key => $val) {
+ if (!is_numeric($key) && in_array($key, $perm_fields)) {
+ $html .= '<tr>';
+ $html .= $yesno[ ($val || $perms[0]['is_admin']) ];
+ $html .= $i==0 ? '<th>'.eL(str_replace('_','',$key)).'</th>' : '';
+ $html .= '</tr>';
+
+ # all projects have same permnames
+ $projpermnames .= $i==1 ? '<tr><td>'.eL(str_replace('_','',$key)).'</td></tr>' : '';
+ }
+ }
+ $html.= '</tbody></table>';
+ $i++;
+ }
+ $html.='<table class="perms"><thead><th>'.L('permissions').'</th></thead><tbody>'.$projpermnames.'</tbody></table>';
+ $html.='<style>.perms tr{height:30px;}</style>';
+ # end 20150307
+ return $html;
+}
+
+/**
+ * Highlights searchqueries in HTML code
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ */
+function html_hilight($html,$query){
+ //split at common delimiters
+ $queries = preg_split ('/[\s\'"\\\\`()\]\[?:!\.{};,#+*<>]+/',$query,-1,PREG_SPLIT_NO_EMPTY);
+ foreach ($queries as $q){
+ $q = preg_quote($q,'/');
+ $html = preg_replace_callback("/((<[^>]*)|$q)/i",'html_hilight_callback',$html);
+ }
+ return $html;
+}
+
+/**
+ * Callback used by html_hilight()
+ *
+ * @author Harry Fuecks <hfuecks@gmail.com>
+ */
+function html_hilight_callback($m) {
+ $hlight = unslash($m[0]);
+ if ( !isset($m[2])) {
+ $hlight = '<span class="search_hit">'.$hlight.'</span>';
+ }
+ return $hlight;
+}
+
+/**
+ * XHTML compatible output of disabled attribute
+ *
+ * @param bool if something that PHP sees as true or false
+ */
+function tpl_disableif ($if)
+{
+ if ($if) {
+ return 'disabled="disabled"';
+ }
+}
+
+/**
+ * Generates links for Flyspray
+ *
+ * Create an URL based upon address-rewriting preferences
+ *
+ */
+function createURL($type, $arg1 = null, $arg2 = null, $arg3 = array())
+{
+ global $baseurl, $conf, $fs;
+
+ $url = $baseurl;
+
+ // If we do want address rewriting
+ if ($fs->prefs['url_rewriting']) {
+ switch ($type) {
+ case 'depends':
+ $return = $url . 'task/' . $arg1 . '/' . $type;
+ break;
+ case 'details':
+ $return = $url . 'task/' . $arg1;
+ break;
+ case 'edittask':
+ $return = $url . 'task/' . $arg1 . '/edit';
+ break;
+ case 'pm':
+ $return = $url . 'pm/proj' . $arg2 . '/' . $arg1;
+ break;
+
+ case 'admin':
+ case 'edituser':
+ case 'user':
+ $return = $url . $type . '/' . $arg1;
+ break;
+
+ case 'project':
+ $return = $url . 'proj' . $arg1;
+ break;
+
+ case 'reports':
+ case 'roadmap':
+ case 'toplevel':
+ case 'gantt':
+ case 'index':
+ $return = $url.$type.'/proj'.$arg1;
+ break;
+
+ case 'newtask':
+ case 'newmultitasks':
+ $return = $url . $type . '/proj' . $arg1 . ($arg2 ? '/supertask' . $arg2 : '');
+ break;
+
+ case 'editgroup':
+ $return = $url . $arg2 . '/' . $type . '/' . $arg1;
+ break;
+
+ case 'logout':
+ case 'lostpw':
+ case 'myprofile':
+ case 'register':
+ $return = $url . $type;
+ break;
+
+ case 'mytasks':
+ $return = $url.'proj'.$arg1.'/dev'.$arg2;
+ break;
+ case 'tasklist':
+ # see also .htaccess for the mapping
+ if($arg1>0 && $fs->projects[$arg1]['default_entry']=='index'){
+ $return = $url.'proj'.$arg1;
+ }else{
+ $return = $url.$type.'/proj'.$arg1;
+ }
+
+ break;
+ default:
+ $return = $baseurl . 'index.php';
+ break;
+ }
+ } else {
+ if ($type == 'edittask') {
+ $url .= 'index.php?do=details';
+ } else {
+ $url .= 'index.php?do=' . $type;
+ }
+
+ switch ($type) {
+ case 'admin':
+ $return = $url . '&area=' . $arg1;
+ break;
+ case 'edittask':
+ $return = $url . '&task_id=' . $arg1 . '&edit=yep';
+ break;
+ case 'pm':
+ $return = $url . '&area=' . $arg1 . '&project=' . $arg2;
+ break;
+ case 'user':
+ $return = $baseurl . 'index.php?do=user&area=users&id=' . $arg1;
+ break;
+ case 'edituser':
+ $return = $baseurl . 'index.php?do=admin&area=users&user_id=' . $arg1;
+ break;
+ case 'logout':
+ $return = $baseurl . 'index.php?do=authenticate&logout=1';
+ break;
+
+ case 'details':
+ case 'depends':
+ $return = $url . '&task_id=' . $arg1;
+ break;
+
+ case 'project':
+ $return = $baseurl . 'index.php?project=' . $arg1;
+ break;
+
+ case 'reports':
+ case 'roadmap':
+ case 'toplevel':
+ case 'gantt':
+ case 'index':
+ case 'tasklist':
+ $return = $url . '&project=' . $arg1;
+ break;
+
+ case 'newtask':
+ case 'newmultitasks':
+ $return = $url . '&project=' . $arg1 . ($arg2 ? '&supertask=' . $arg2 : '');
+ break;
+
+ case 'editgroup':
+ $return = $baseurl . 'index.php?do=' . $arg2 . '&area=editgroup&id=' . $arg1;
+ break;
+
+ case 'lostpw':
+ case 'myprofile':
+ case 'register':
+ $return = $url;
+ break;
+
+ case 'mytasks':
+ $return = $baseurl.'index.php?do=index&project='.$arg1.'&dev='.$arg2;
+ break;
+
+ default:
+ $return = $baseurl . 'index.php';
+ break;
+ }
+ }
+
+ $url = new Url($return);
+ if( !is_null($arg3) && count($arg3) ) {
+ $url->addvars($arg3);
+ }
+ return $url->get();
+}
+
+/**
+ * Page numbering
+ *
+ * Thanks to Nathan Fritz for this. http://www.netflint.net/
+ */
+function pagenums($pagenum, $perpage, $totalcount)
+{
+ global $proj;
+ $pagenum = intval($pagenum);
+ $perpage = intval($perpage);
+ $totalcount = intval($totalcount);
+
+ // Just in case $perpage is something weird, like 0, fix it here:
+ if ($perpage < 1) {
+ $perpage = $totalcount > 0 ? $totalcount : 1;
+ }
+ $pages = ceil($totalcount / $perpage);
+ $output = sprintf(eL('page'), $pagenum, $pages);
+
+ if ( $totalcount / $perpage > 1 ) {
+ $params=$_GET;
+ # unset unneeded params for shorter urls
+ unset($params['do']);
+ unset($params['project']);
+ unset($params['switch']);
+ $output .= '<span class="pagenums DoNotPrint">';
+
+ $start = max(1, $pagenum - 4 + min(2, $pages - $pagenum));
+ $finish = min($start + 4, $pages);
+
+ if ($start > 1) {
+ $url = Filters::noXSS(createURL('tasklist', $proj->id, null, array_merge($params, array('pagenum' => 1))));
+ $output .= sprintf('<a href="%s">&lt;&lt;%s </a>', $url, eL('first'));
+ }
+ if ($pagenum > 1) {
+ $url = Filters::noXSS(createURL('tasklist', $proj->id, null, array_merge($params, array('pagenum' => $pagenum - 1))));
+ $output .= sprintf('<a id="previous" accesskey="p" href="%s">&lt; %s</a> - ', $url, eL('previous'));
+ }
+
+ for ($pagelink = $start; $pagelink <= $finish; $pagelink++) {
+ if ($pagelink != $start) {
+ $output .= ' - ';
+ }
+
+ if ($pagelink == $pagenum) {
+ $output .= sprintf('<strong>%d</strong>', $pagelink);
+ } else {
+ $url = Filters::noXSS(createURL('tasklist', $proj->id, null, array_merge($params, array('pagenum' => $pagelink))));
+ $output .= sprintf('<a href="%s">%d</a>', $url, $pagelink);
+ }
+ }
+
+ if ($pagenum < $pages) {
+ $url = Filters::noXSS(createURL('tasklist', $proj->id, null, array_merge($params, array('pagenum' => $pagenum + 1))));
+ $output .= sprintf(' - <a id="next" accesskey="n" href="%s">%s &gt;</a>', $url, eL('next'));
+ }
+ if ($finish < $pages) {
+ $url = Filters::noXSS(createURL('tasklist', $proj->id, null, array_merge($params, array('pagenum' => $pages))));
+ $output .= sprintf('<a href="%s"> %s &gt;&gt;</a>', $url, eL('last'));
+ }
+ $output .= '</span>';
+ }
+
+ return $output;
+}
+
+class Url {
+ public $url = '';
+ public $parsed;
+
+ public function __construct($url = '') {
+ $this->url = $url;
+ $this->parsed = parse_url($this->url);
+ }
+
+ public function seturl($url) {
+ $this->url = $url;
+ $this->parsed = parse_url($this->url);
+ }
+
+ public function getinfo($type = null) {
+ if (is_null($type)) {
+ return $this->parsed;
+ } elseif (isset($this->parsed[$type])) {
+ return $this->parsed[$type];
+ } else {
+ return '';
+ }
+ }
+
+ public function setinfo($type, $value) {
+ $this->parsed[$type] = $value;
+ }
+
+ public function addfrom($method = 'get', $vars = array()) {
+ $append = '';
+ foreach($vars as $key) {
+ $append .= http_build_query( (($method == 'get') ? Get::val($key) : Post::val($key)) ) . '&';
+ }
+ $append = substr($append, 0, -1);
+
+ $separator = ini_get('arg_separator.output');
+ if (strlen($separator) != 0) {
+ $append = str_replace($separator, '&', $append);
+ }
+
+ if ($this->getinfo('query')) {
+ $this->parsed['query'] .= '&' . $append;
+ } else {
+ $this->parsed['query'] = $append;
+ }
+ }
+
+ public function addvars($vars = array()) {
+ $append = http_build_query($vars);
+
+ $separator = ini_get('arg_separator.output');
+ if (strlen($separator) != 0) {
+ $append = str_replace($separator, '&', $append);
+ }
+
+ if ($this->getinfo('query')) {
+ $this->parsed['query'] .= '&' . $append;
+ } else {
+ $this->parsed['query'] = $append;
+ }
+ }
+
+ public function get($fullpath = true) {
+ $return = '';
+ if ($fullpath) {
+ $return .= $this->getinfo('scheme') . '://' . $this->getinfo('host');
+
+ if ($this->getinfo('port')) {
+ $return .= ':' . $this->getinfo('port');
+ }
+ }
+
+ $return .= $this->getinfo('path');
+
+ if ($this->getinfo('query')) {
+ $return .= '?' . $this->getinfo('query');
+ }
+
+ if ($this->getinfo('fragment')) {
+ $return .= '#' . $this->getinfo('fragment');
+ }
+
+ return $return;
+ }
+}