1<?php 2/** 3 * EGroupware - eTemplate serverside template widget 4 * 5 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License 6 * @package api 7 * @subpackage etemplate 8 * @link http://www.egroupware.org 9 * @author Ralf Becker <RalfBecker@outdoor-training.de> 10 * @copyright 2002-16 by RalfBecker@outdoor-training.de 11 * @version $Id$ 12 */ 13 14namespace EGroupware\Api\Etemplate\Widget; 15 16use EGroupware\Api\Etemplate; 17use EGroupware\Api; 18use XMLReader; 19 20/* allow to call direct for tests (see end of class) 21if (!isset($GLOBALS['egw_info'])) 22{ 23 $GLOBALS['egw_info'] = array( 24 'flags' => array( 25 'currentapp' => 'login', 26 'debug' => 'etemplate_widget_template', 27 ) 28 ); 29 include_once '../../header.inc.php'; 30} */ 31 32/** 33 * eTemplate widget baseclass 34 */ 35class Template extends Etemplate\Widget 36{ 37 /** 38 * Cache of already read templates 39 * 40 * @var array with name => template pairs 41 */ 42 protected static $cache = array(); 43 44 /** 45 * Path of template relative to EGW_SERVER_ROOT 46 * 47 * @var string 48 */ 49 public $rel_path; 50 51 /** 52 * Get instance of template specified by name, template(-set) and version 53 * 54 * @param string $_name 55 * @param string $template_set =null default try template-set from user and if not found "default" 56 * @param string $version ='' 57 * @param string $load_via ='' use given template to load $name 58 * @return Template|boolean false if not found 59 */ 60 public static function instance($_name, $template_set=null, $version='', $load_via='') 61 { 62 if (Api\Header\UserAgent::mobile()) 63 { 64 $template_set = "mobile"; 65 } 66 67 //$start = microtime(true); 68 list($name) = explode('?', $_name); // remove optional cache-buster 69 if (isset(self::$cache[$name]) || !($path = self::relPath($name, $template_set, $version))) 70 { 71 if ((!$path || self::read($load_via, $template_set)) && isset(self::$cache[$name])) 72 { 73 //error_log(__METHOD__."('$name', '$template_set', '$version', '$load_via') read from cache"); 74 return self::$cache[$name]; 75 } 76 // Template not found, try again as if $name were a partial name 77 else if(!$path && strpos($name,'.') === false) 78 { 79 foreach(self::$cache as $c_name => $c_template) 80 { 81 list(,, $c_sub) = explode('.',$c_name, 3); 82 if($name == $c_sub) 83 { 84 //error_log(__METHOD__ . "('$name' loaded from cache ($c_name)"); 85 return $c_template; 86 } 87 88 $parts = explode('.',$c_name); 89 if($name == $parts[count($parts)-1]) return $c_template; 90 } 91 } 92 // Template not found, try again with content expansion 93 if (is_array(self::$request->content)) 94 { 95 $expand_name = self::expand_name($name, '','','','',self::$cont); 96 if ($expand_name && $expand_name != $name && 97 ($template = self::instance($expand_name, $template_set, $version, $load_via))) 98 { 99 // Remember original, un-expanded name in case content changes while still cached 100 $template->original_name = $name; 101 return $template; 102 } 103 } 104 105 error_log(__METHOD__."('$name', '$template_set', '$version', '$load_via') template NOT found!"); 106 return false; 107 } 108 $reader = new XMLReader(); 109 if (!$reader->open(self::rel2path($path))) return false; 110 111 while($reader->read()) 112 { 113 if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == 'template') 114 { 115 $template = new Template($reader); 116 $template->rel_path = $path; 117 //echo $template->id; _debug_array($template); 118 119 self::$cache[$template->id] = $template; 120 121 if ($template->id == $name) 122 { 123 //error_log(__METHOD__."('$name', '$template_set', '$version', '$load_via') read in ".round(1000.0*(microtime(true)-$start),2)." ms"); 124 return $template; 125 } 126 } 127 } 128 129 // template not found in file, should never happen 130 error_log(__METHOD__."('$name', '$template_set', '$version', '$load_via') template NOT found in file '$path'!"); 131 return false; 132 } 133 134 const VFS_TEMPLATE_PATH = '/etemplates'; 135 136 /** 137 * Get path/URL relative to EGroupware install of a template of full vfs url 138 * 139 * @param string $name 140 * @param string $template_set =null default try template-set from user and if not found "default" 141 * @param string $version ='' 142 * @return string path of template xml file or null if not found 143 */ 144 public static function relPath($name, $template_set=null, $version='') 145 { 146 static $prefixes = null; 147 unset($version); // not used currently 148 list($app, $rest) = explode('.', $name, 2); 149 150 if (empty($template_set)) 151 { 152 $template_set = $GLOBALS['egw_info']['user']['preferences']['common']['template_set']; 153 } 154 $template_path = '/'.$app.'/templates/'.$template_set.'/'.$rest.'.xet'; 155 $default_path = '/'.$app.'/templates/default/'.$rest.'.xet'; 156 157 // check if /etemplates is mounted in VFS and prefer it in that case over phy. file-system 158 if (!isset($prefixes)) 159 { 160 $prefixes = array(EGW_SERVER_ROOT); 161 $fs_tab = Api\Vfs::mount(); 162 if (isset($fs_tab[self::VFS_TEMPLATE_PATH])) 163 { 164 array_unshift($prefixes, Api\Vfs::PREFIX.self::VFS_TEMPLATE_PATH); 165 } 166 } 167 foreach($prefixes as $prefix) 168 { 169 if (file_exists($prefix.$template_path)) 170 { 171 $path = $template_path; 172 break; 173 } 174 if (file_exists($prefix.$default_path)) 175 { 176 $path = $default_path; 177 break; 178 } 179 } 180 // for a vfs template path we keep the prefix, to be able to distinquish between real filesystem and vfs 181 if (isset($path) && $prefix !== EGW_SERVER_ROOT) 182 { 183 $path = $prefix.$path; 184 } 185 //error_log(__METHOD__."('$name', '$template_set') returning ".array2string($path)); 186 return $path; 187 } 188 189 /** 190 * Convert relative template path from relPath to an absolute path 191 * 192 * @param string $path 193 * @return string 194 */ 195 public static function rel2path($path) 196 { 197 if ($path[0] === '/') 198 { 199 $path = EGW_SERVER_ROOT.$path; 200 } 201 return $path; 202 } 203 204 /** 205 * Convert relative template path from relPath to an url incl. cache-buster modification time postfix 206 * 207 * @param string $path 208 * @return string url 209 */ 210 public static function rel2url($path) 211 { 212 if ($path) 213 { 214 if ($path[0] === '/') 215 { 216 $url = $GLOBALS['egw_info']['server']['webserver_url'].$path.'?'.filemtime(self::rel2path($path)); 217 } 218 else 219 { 220 $url = Api\Vfs::download_url($path); 221 222 if ($url[0] == '/') $url = Api\Framework::link($url); 223 224 // mtime postfix has to use '?download=', as our WebDAV treats everything else literal and not ignore them like Apache for static files! 225 $url .= '?download='.filemtime($path); 226 } 227 } 228 //error_log(__METHOD__."('$path') returning $url"); 229 return $url; 230 } 231 232 /** 233 * Run method on all children 234 * 235 * Reimplemented because templates can have an own namespace specified in attrs[content], NOT id! 236 * 237 * @param string|callable $method_name or function($cname, $expand, $widget) 238 * @param array $params =array('') parameter(s) first parameter has to be cname, second $expand! 239 * @param boolean $respect_disabled =false false (default): ignore disabled, true: method is NOT run for disabled widgets AND their children 240 */ 241 public function run($method_name, $params=array(''), $respect_disabled=false) 242 { 243 $cname =& $params[0]; 244 $old_cname = $params[0]; 245 if ($this->attrs['content']) $cname = self::form_name($cname, $this->attrs['content'], $params[1]); 246 247 // Check for template from content, and run over it 248 // templates included via template tag have their name to load them from in attribute "template" 249 $expand_name = self::expand_name($this->id ? $this->id : $this->attrs['template'], '','','','',self::$request->content); 250 if(!$expand_name && $this->id && $this->attrs['template']) 251 { 252 $expand_name = $this->attrs['template']; 253 } 254 if($this->original_name) 255 { 256 $expand_name = self::expand_name($this->original_name, '','','','',self::$request->content); 257 } 258 //error_log("$this running $method_name() cname: {$this->id} -> expand_name: $expand_name"); 259 if($expand_name && $expand_name != $this->id) 260 { 261 if (($row_template = self::instance($expand_name))) 262 { 263 $row_template->run($method_name, $params, $respect_disabled); 264 } 265 } 266 else 267 { 268 parent::run($method_name, $params, $respect_disabled); 269 } 270 $params[0] = $old_cname; 271 } 272 273 /** 274 * Fill type options in self::$request->sel_options to be used on the client 275 * 276 * @param string $cname 277 * @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont' 278 */ 279 public function beforeSendToClient($cname, array $expand=null) 280 { 281 //error_log(__METHOD__."('$cname') this->id=$this->id, this->type=$this->type, this->attrs=".array2string($this->attrs)); 282 $form_name = self::form_name($cname, $this->id, $expand); 283 284 self::setElementAttribute($form_name, 'url', self::rel2url($this->rel_path)); 285 } 286} 287 288/* 289if ($GLOBALS['egw_info']['flags']['debug'] == 'etemplate_widget_template') 290{ 291 $name = isset($_GET['name']) ? $_GET['name'] : 'timesheet.edit'; 292 if (!($template = Template::instance($name))) 293 { 294 header('HTTP-Status: 404 Not Found'); 295 echo "<html><head><title>Not Found</title><body><h1>Not Found</h1><p>The requested eTemplate '$name' was not found!</p></body></html>\n"; 296 exit; 297 } 298 header('Content-Type: text/xml'); 299 echo $template->toXml(); 300} 301*/