_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 '
'. ( $method=='post' ? '':''); } /** * 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('%s',$url, $title_text, join_attrs($attrs), $text); if ($task['is_closed']) { $link = ' ' . $link . ' '; } 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('%s', 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 ''; } 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 = ''; } 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 = ''; } else { $image = ''; } } 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] = ''.$image.''; } } 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=''; } $out.=''; 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 '; 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).'$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).''; } else{ # we have a simple option $html.="\n".str_repeat("\t",$level).''; } } 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('', $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('', $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(''; } /** * Image display */ function tpl_img($src, $alt = '') { global $baseurl; if (is_file(BASEDIR .'/'.$src)) { return sprintf('%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, '

    1. '); if ( $conf['general']['syntax_plugin'] && $conf['general']['syntax_plugin'] != 'none' && $conf['general']['syntax_plugin'] != 'html') { $text='Unsupported output plugin '.$conf['general']['syntax_plugin'].'!' .'
      Couldn\'t call '.$conf['general']['syntax_plugin'].'_TextFormatter::render()' .'
      Temporarily handled like it is HTML until fixed.
      ' .$text; } //TODO: Remove Redundant Code once tested completely //Author: Steve Tredinnick //Have removed this as creating additional
      lines even though

      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('