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*/