1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* EGroupware - eTemplate serverside |
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 <[email protected]> |
10
|
|
|
* @copyright 2002-16 by [email protected] |
11
|
|
|
* @version $Id$ |
12
|
|
|
*/ |
13
|
|
|
|
14
|
|
|
namespace EGroupware\Api; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* New eTemplate serverside contains: |
18
|
|
|
* - main server methods like read, exec |
19
|
|
|
* - |
20
|
|
|
* |
21
|
|
|
* Not longer available methods: |
22
|
|
|
* - set_(row|column)_attributes modifies template on run-time, was only used internally by etemplate itself |
23
|
|
|
* - disable_(row|column) dto. |
24
|
|
|
*/ |
25
|
|
|
class Etemplate extends Etemplate\Widget\Template |
26
|
|
|
{ |
27
|
|
|
/** |
28
|
|
|
* Are we running as sitemgr module or not |
29
|
|
|
* |
30
|
|
|
* @public boolean |
31
|
|
|
*/ |
32
|
|
|
public $sitemgr=false; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Tell egw framework it's ok to call this |
36
|
|
|
*/ |
37
|
|
|
public $public_functions = array( |
38
|
|
|
'process_exec' => true |
39
|
|
|
); |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* constructor of etemplate class, reads an eTemplate if $name is given |
43
|
|
|
* |
44
|
|
|
* @param string $name of etemplate or array with name and other keys |
45
|
|
|
* @param string|array $load_via with keys of other etemplate to load in order to get $name |
46
|
|
|
*/ |
47
|
|
|
function __construct($name='',$load_via='') |
48
|
|
|
{ |
49
|
|
|
// we do NOT call parent consturctor, as we only want to enherit it's (static) methods |
50
|
|
|
if (false) parent::__construct ($name); // satisfy IDE, as we dont call parent constructor |
51
|
|
|
|
52
|
|
|
$this->sitemgr = isset($GLOBALS['Common_BO']) && is_object($GLOBALS['Common_BO']); |
53
|
|
|
|
54
|
|
|
if ($name) $this->read($name,$template='default','default',0,'',$load_via); |
55
|
|
|
|
56
|
|
|
// generate new etemplate request object, if not already existing |
57
|
|
|
if(!isset(self::$request)) self::$request = Etemplate\Request::read(); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Abstracts a html-location-header call |
62
|
|
|
* |
63
|
|
|
* In other UI's than html this needs to call the methode, defined by menuaction or |
64
|
|
|
* open a browser-window for any other links. |
65
|
|
|
* |
66
|
|
|
* @param string|array $params url or array with get-params incl. menuaction |
67
|
|
|
*/ |
68
|
|
|
static function location($params='') |
69
|
|
|
{ |
70
|
|
|
Framework::redirect_link(is_array($params) ? '/index.php' : $params, |
71
|
|
|
is_array($params) ? $params : ''); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* Generates a Dialog from an eTemplate - abstract the UI-layer |
76
|
|
|
* |
77
|
|
|
* This is the only function an application should use, all other are INTERNAL and |
78
|
|
|
* do NOT abstract the UI-layer, because they return HTML. |
79
|
|
|
* Generates a webpage with a form from the template and puts process_exec in the |
80
|
|
|
* form as submit-url to call process_show for the template before it |
81
|
|
|
* ExecuteMethod's the given $method of the caller. |
82
|
|
|
* |
83
|
|
|
* @param string $method Methode (e.g. 'etemplate.editor.edit') to be called if form is submitted |
84
|
|
|
* @param array $content with content to fill the input-fields of template, eg. the text-field |
85
|
|
|
* with name 'name' gets its content from $content['name'] |
86
|
|
|
* @param $sel_options array or arrays with the options for each select-field, keys are the |
87
|
|
|
* field-names, eg. array('name' => array(1 => 'one',2 => 'two')) set the |
88
|
|
|
* options for field 'name'. ($content['options-name'] is possible too !!!) |
89
|
|
|
* @param array $readonlys with field-names as keys for fields with should be readonly |
90
|
|
|
* (eg. to implement ACL grants on field-level or to remove buttons not applicable) |
91
|
|
|
* @param array $preserv with vars which should be transported to the $method-call (eg. an id) array('id' => $id) sets $_POST['id'] for the $method-call |
92
|
|
|
* @param int $output_mode |
93
|
|
|
* 0 = echo incl. navbar |
94
|
|
|
* 1 = return html |
95
|
|
|
* -1 = first time return html, after use 0 (echo html incl. navbar), eg. for home |
96
|
|
|
* 2 = echo without navbar (eg. for popups) |
97
|
|
|
* 3 = return eGW independent html site |
98
|
|
|
* 4 = json response |
99
|
|
|
* @param string $ignore_validation if not empty regular expression for validation-errors to ignore |
100
|
|
|
* @param array $changes change made in the last call if looping, only used internaly by process_exec |
101
|
|
|
* @return string html for $output_mode == 1, else nothing |
102
|
|
|
*/ |
103
|
|
|
function exec($method,array $content,array $sel_options=null,array $readonlys=null,array $preserv=null,$output_mode=0,$ignore_validation='',array $changes=null) |
104
|
|
|
{ |
105
|
|
|
$hook_data = Hooks::process( |
106
|
|
|
array('hook_location' => 'etemplate2_before_exec') + |
107
|
|
|
array('location_name' => $this->name) + |
108
|
|
|
array('location_object' => &$this) + |
109
|
|
|
$content |
110
|
|
|
); |
111
|
|
|
|
112
|
|
|
foreach($hook_data as $extras) |
113
|
|
|
{ |
114
|
|
|
if (!$extras) continue; |
115
|
|
|
|
116
|
|
|
foreach(isset($extras[0]) ? $extras : array($extras) as $extra) |
117
|
|
|
{ |
118
|
|
|
if ($extra['data'] && is_array($extra['data'])) |
119
|
|
|
{ |
120
|
|
|
$content = array_merge($content, $extra['data']); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
if ($extra['preserve'] && is_array($extra['preserve'])) |
124
|
|
|
{ |
125
|
|
|
$preserv = array_merge($preserv, $extra['preserve']); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
if ($extra['readonlys'] && is_array($extra['readonlys'])) |
129
|
|
|
{ |
130
|
|
|
$readonlys = array_merge($readonlys, $extra['readonlys']); |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
if ($extra['sel_options'] && is_array($extra['sel_options'])) |
134
|
|
|
{ |
135
|
|
|
$sel_options = array_merge($sel_options, $extra['sel_options']); |
136
|
|
|
} |
137
|
|
|
} |
138
|
|
|
} |
139
|
|
|
unset($hook_data); |
140
|
|
|
|
141
|
|
|
// Include the etemplate2 javascript code |
142
|
|
|
Framework::includeJS('etemplate', 'etemplate2', 'api'); |
143
|
|
|
|
144
|
|
|
if (!$this->rel_path) throw new Exception\AssertionFailed("No (valid) template '$this->name' found!"); |
145
|
|
|
|
146
|
|
|
if ($output_mode == 4) |
147
|
|
|
{ |
148
|
|
|
$output_mode = 0; |
149
|
|
|
self::$response = Json\Response::get(); |
150
|
|
|
} |
151
|
|
|
self::$request->output_mode = $output_mode; // let extensions "know" they are run eg. in a popup |
152
|
|
|
self::$request->content = self::$cont = $content; |
153
|
|
|
self::$request->changes = $changes; |
154
|
|
|
self::$request->sel_options = is_array($sel_options) ? self::fix_sel_options($sel_options) : array(); |
155
|
|
|
self::$request->readonlys = $readonlys ? $readonlys : array(); |
156
|
|
|
self::$request->preserv = $preserv ? $preserv : array(); |
157
|
|
|
self::$request->method = $method; |
158
|
|
|
self::$request->ignore_validation = $ignore_validation; |
159
|
|
|
if (self::$request->output_mode == -1) self::$request->output_mode = 0; |
160
|
|
|
self::$request->template = $this->as_array(); |
161
|
|
|
|
162
|
|
|
if (empty($this->name)) throw new Exception\AssertionFailed("Template name is not set '$this->name' !"); |
163
|
|
|
// instanciate template to fill self::$request->sel_options for select-* widgets |
164
|
|
|
// not sure if we want to handle it this way, thought otherwise we will have a few ajax request for each dialog fetching predefined selectboxes |
165
|
|
|
$template = self::instance($this->name, $this->template_set, $this->version, $this->laod_via); |
166
|
|
|
if (!$template) throw new Exception\AssertionFailed("Template $this->name not instanciable! Maybe you forgot to rename template id."); |
167
|
|
|
$this->children = array($template); |
168
|
|
|
$template->run('beforeSendToClient', array('', array('cont'=>$content))); |
169
|
|
|
|
170
|
|
|
// some apps (eg. InfoLog) set app_header only in get_rows depending on filter settings |
171
|
|
|
self::$request->app_header = $GLOBALS['egw_info']['flags']['app_header']; |
172
|
|
|
|
173
|
|
|
// compile required translations translations |
174
|
|
|
$currentapp = $GLOBALS['egw_info']['flags']['currentapp']; |
175
|
|
|
$langRequire = array('common' => array(), 'etemplate' => array()); // keep that order |
176
|
|
|
foreach(Translation::$loaded_apps as $l_app => $lang) |
177
|
|
|
{ |
178
|
|
|
if (!in_array($l_app, array($currentapp, 'custom'))) |
179
|
|
|
{ |
180
|
|
|
$langRequire[$l_app] = array('app' => $l_app, 'lang' => $lang, 'etag' => Translation::etag($l_app, $lang)); |
181
|
|
|
} |
182
|
|
|
} |
183
|
|
|
foreach(array($currentapp, 'custom') as $l_app) |
184
|
|
|
{ |
185
|
|
|
if (isset(Translation::$loaded_apps[$l_app])) |
186
|
|
|
{ |
187
|
|
|
$langRequire[$l_app] = array('app' => $l_app, 'lang' => Translation::$loaded_apps[$l_app], 'etag' => Translation::etag($l_app, Translation::$loaded_apps[$l_app])); |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
$data = array( |
192
|
|
|
'etemplate_exec_id' => self::$request->id(), |
193
|
|
|
'app_header' => self::$request->app_header, |
194
|
|
|
'content' => self::$request->content, |
195
|
|
|
'sel_options' => self::$request->sel_options, |
196
|
|
|
'readonlys' => self::$request->readonlys, |
197
|
|
|
'modifications' => self::$request->modifications, |
198
|
|
|
'validation_errors' => self::$validation_errors, |
199
|
|
|
'langRequire' => array_values($langRequire), |
200
|
|
|
'currentapp' => $currentapp, |
201
|
|
|
); |
202
|
|
|
|
203
|
|
|
if($data['content']['nm']['rows'] && is_array($data['content']['nm']['rows'])) |
204
|
|
|
{ |
205
|
|
|
// Deep copy rows so we don't lose them when request is set to null |
206
|
|
|
// (some content by reference) |
207
|
|
|
$data['content']['nm'] = self::deep_copy($data['content']['nm']); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
// Info required to load the etemplate client-side |
211
|
|
|
$dom_id = str_replace('.','-',$this->dom_id); |
212
|
|
|
$load_array = array( |
213
|
|
|
'name' => $this->name, |
214
|
|
|
'url' => self::rel2url($this->rel_path), |
215
|
|
|
'data' => $data, |
216
|
|
|
'DOMNodeID' => $dom_id, |
217
|
|
|
); |
218
|
|
|
if (self::$response) // call is within an ajax event / form submit |
219
|
|
|
{ |
220
|
|
|
//error_log("Ajax " . __LINE__); |
221
|
|
|
self::$response->generic('et2_load', $load_array+Framework::get_extra()); |
222
|
|
|
Framework::clear_extra(); // to not send/set it twice for multiple etemplates (eg. CRM view) |
223
|
|
|
} |
224
|
|
|
else // first call |
225
|
|
|
{ |
226
|
|
|
// check if application of template has a app.js file --> load it |
227
|
|
|
list($app) = explode('.',$this->name); |
228
|
|
|
if (file_exists(EGW_SERVER_ROOT.'/'.$app.'/js/app.js')) |
229
|
|
|
{ |
230
|
|
|
Framework::includeJS('.','app',$app,true); |
231
|
|
|
} |
232
|
|
|
// Category styles |
233
|
|
|
Categories::css($app); |
234
|
|
|
|
235
|
|
|
// set action attribute for autocomplete form tag |
236
|
|
|
// as firefox complains on about:balnk action, thus we have to literaly submit the form to a blank html |
237
|
|
|
$form_action = "about:blank"; |
238
|
|
|
if (in_array(Header\UserAgent::type(), array('firefox', 'safari'))) |
239
|
|
|
{ |
240
|
|
|
$form_action = $GLOBALS['egw_info']['server']['webserver_url'].'/api/templates/default/empty.html'; |
241
|
|
|
} |
242
|
|
|
// check if we are in an ajax-exec call from jdots template (or future other tabbed templates) |
243
|
|
|
if (isset($GLOBALS['egw']->framework->response)) |
244
|
|
|
{ |
245
|
|
|
$content = '<form target="egw_iframe_autocomplete_helper" action="'.$form_action.'" id="'.$dom_id.'" class="et2_container"></form>'."\n". |
246
|
|
|
'<iframe name="egw_iframe_autocomplete_helper" style="width:0;height:0;position: absolute;visibility:hidden;"></iframe>'; |
247
|
|
|
$GLOBALS['egw']->framework->response->generic("data", array($content)); |
248
|
|
|
$GLOBALS['egw']->framework->response->generic('et2_load',$load_array+Framework::get_extra()); |
249
|
|
|
Framework::clear_extra(); // to not send/set it twice for multiple etemplates (eg. CRM view) |
250
|
|
|
|
251
|
|
|
// Really important to run this or weird things happen |
252
|
|
|
// See https://help.egroupware.org/t/nextmatch-wert-im-header-ausgeben/73412/11 |
253
|
|
|
self::$request=null; |
254
|
|
|
return; |
255
|
|
|
} |
256
|
|
|
// let framework know, if we are a popup or not ('popup' not true, which is allways used by index.php!) |
257
|
|
|
if (!isset($GLOBALS['egw_info']['flags']['nonavbar']) || is_bool($GLOBALS['egw_info']['flags']['nonavbar'])) |
258
|
|
|
{ |
259
|
|
|
$GLOBALS['egw_info']['flags']['nonavbar'] = $output_mode == 2 ? 'popup' : false; |
260
|
|
|
} |
261
|
|
|
echo $GLOBALS['egw']->framework->header(); |
262
|
|
|
if ($output_mode != 2 && !$GLOBALS['egw_info']['flags']['nonavbar']) |
263
|
|
|
{ |
264
|
|
|
parse_navbar(); |
|
|
|
|
265
|
|
|
} |
266
|
|
|
else // mark popups as such, by enclosing everything in div#popupMainDiv |
267
|
|
|
{ |
268
|
|
|
echo '<div id="popupMainDiv" class="popupMainDiv">'."\n"; |
269
|
|
|
} |
270
|
|
|
// Send any accumulated json responses - after flush to avoid sending the buffer as a response |
271
|
|
|
if(Json\Response::isJSONResponse()) |
272
|
|
|
{ |
273
|
|
|
$load_array['response'] = Json\Response::get()->returnResult(); |
274
|
|
|
} |
275
|
|
|
// <iframe> and <form> tags added only to get browser autocomplete handling working again |
276
|
|
|
$form = '<form target="egw_iframe_autocomplete_helper" action="'.$form_action.'" id="'.$dom_id.'" class="et2_container" data-etemplate="'. |
277
|
|
|
htmlspecialchars(Json\Response::json_encode($load_array), ENT_COMPAT, Translation::charset(), true).'"></form>'."\n". |
278
|
|
|
'<iframe name="egw_iframe_autocomplete_helper" style="width:0;height:0;position: absolute;visibility:hidden;"></iframe>'; |
279
|
|
|
echo $form; |
280
|
|
|
|
281
|
|
|
if ($output_mode == 2) |
282
|
|
|
{ |
283
|
|
|
echo "\n</div>\n"; |
284
|
|
|
echo $GLOBALS['egw']->framework->footer(); |
285
|
|
|
} |
286
|
|
|
ob_flush(); |
287
|
|
|
if ($output_mode == 1) return $form; |
288
|
|
|
} |
289
|
|
|
self::$request = null; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Fix all sel_options, as Etemplate\Widget\Select::beforeSendToClient is not run for auto-repeated stuff not understood by server |
294
|
|
|
* |
295
|
|
|
* @param array $sel_options |
296
|
|
|
* @return array |
297
|
|
|
*/ |
298
|
|
|
static protected function fix_sel_options(array $sel_options) |
299
|
|
|
{ |
300
|
|
|
foreach($sel_options as &$options) |
301
|
|
|
{ |
302
|
|
|
if (!is_array($options)||empty($options)) continue; |
303
|
|
|
foreach($options as $key => $value) |
304
|
|
|
{ |
305
|
|
|
if (is_numeric($key) && (!is_array($value) || !isset($value['value']))) |
306
|
|
|
{ |
307
|
|
|
Etemplate\Widget\Select::fix_encoded_options($options, true); |
308
|
|
|
break; |
309
|
|
|
} |
310
|
|
|
} |
311
|
|
|
} |
312
|
|
|
return $sel_options; |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* Process via Ajax submitted content |
317
|
|
|
* |
318
|
|
|
* @param string $etemplate_exec_id |
319
|
|
|
* @param array $_content |
320
|
|
|
* @param boolean $no_validation |
321
|
|
|
* @throws Exception\WrongParameter |
322
|
|
|
*/ |
323
|
|
|
static public function ajax_process_content($etemplate_exec_id, array $_content, $no_validation) |
324
|
|
|
{ |
325
|
|
|
//error_log(__METHOD__."(".array2string($etemplate_exec_id).', '.array2string($_content).")"); |
326
|
|
|
|
327
|
|
|
self::$request = Etemplate\Request::read($etemplate_exec_id); |
328
|
|
|
//error_log('request='.array2string(self::$request)); |
329
|
|
|
|
330
|
|
|
self::$response = Json\Response::get(); |
331
|
|
|
|
332
|
|
View Code Duplication |
if (!($template = self::instance(self::$request->template['name'], self::$request->template['template_set'], |
333
|
|
|
self::$request->template['version'], self::$request->template['load_via']))) |
334
|
|
|
{ |
335
|
|
|
throw new Exception\WrongParameter('Can NOT read template '.array2string(self::$request->template)); |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
// let session class know, which is the app & method of this request |
339
|
|
|
$GLOBALS['egw']->session->set_action('Etemplate: '.self::$request->method); |
340
|
|
|
|
341
|
|
|
// Set current app for validation |
342
|
|
|
list($app) = explode('.',self::$request->method); |
343
|
|
|
if(!$app) list($app) = explode('::',self::$request->method); |
344
|
|
|
if($app) |
345
|
|
|
{ |
346
|
|
|
Translation::add_app($app); |
347
|
|
|
$GLOBALS['egw_info']['flags']['currentapp'] = $app; |
348
|
|
|
} |
349
|
|
|
$validated = array(); |
350
|
|
|
$expand = array( |
351
|
|
|
'cont' => &self::$request->content, |
352
|
|
|
); |
353
|
|
|
$template->run('validate', array('', $expand, $_content, &$validated), true); // $respect_disabled=true: do NOT validate disabled widgets and children |
354
|
|
|
|
355
|
|
|
if ($no_validation) |
356
|
|
|
{ |
357
|
|
|
self::$validation_errors = array(); |
358
|
|
|
} |
359
|
|
|
elseif (self::validation_errors(self::$request->ignore_validation)) |
360
|
|
|
{ |
361
|
|
|
//error_log(__METHOD__."(,".array2string($_content).') validation_errors='.array2string(self::$validation_errors)); |
362
|
|
|
self::$response->generic('et2_validation_error', self::$validation_errors); |
363
|
|
|
return; |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
// tell request call to remove request, if it is not modified eg. by call to exec in callback |
367
|
|
|
self::$request->remove_if_not_modified(); |
368
|
|
|
|
369
|
|
|
foreach(Hooks::process(array( |
370
|
|
|
'hook_location' => 'etemplate2_before_process', |
371
|
|
|
'location_name' => $template->id, |
372
|
|
|
) + self::complete_array_merge(self::$request->preserv, $validated)) as $extras) |
373
|
|
|
{ |
374
|
|
|
if (!$extras) continue; |
375
|
|
|
|
376
|
|
View Code Duplication |
foreach(isset($extras[0]) ? $extras : array($extras) as $extra) |
377
|
|
|
{ |
378
|
|
|
if ($extra['data'] && is_array($extra['data'])) |
379
|
|
|
{ |
380
|
|
|
$validated = array_merge($validated, $extra['data']); |
381
|
|
|
} |
382
|
|
|
} |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
//error_log(__METHOD__."(,".array2string($content).')'); |
386
|
|
|
//error_log(' validated='.array2string($validated)); |
387
|
|
|
if(is_callable(self::$request->method)) |
388
|
|
|
{ |
389
|
|
|
call_user_func(self::$request->method,self::complete_array_merge(self::$request->preserv, $validated)); |
390
|
|
|
} |
391
|
|
|
else |
392
|
|
|
{ |
393
|
|
|
// Deprecated, but may still be needed |
394
|
|
|
$content = ExecMethod(self::$request->method, self::complete_array_merge(self::$request->preserv, $validated)); |
|
|
|
|
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
$tcontent = is_array($content) ? $content : |
398
|
|
|
self::complete_array_merge(self::$request->preserv, $validated); |
399
|
|
|
|
400
|
|
|
$hook_data = Hooks::process( |
401
|
|
|
array( |
402
|
|
|
'hook_location' => 'etemplate2_after_process', |
403
|
|
|
'location_name' => $template->id |
404
|
|
|
) + $tcontent); |
405
|
|
|
|
406
|
|
|
unset($tcontent); |
407
|
|
|
|
408
|
|
|
if (is_array($content)) |
409
|
|
|
{ |
410
|
|
|
foreach($hook_data as $extras) |
411
|
|
|
{ |
412
|
|
|
if (!$extras) continue; |
413
|
|
|
|
414
|
|
View Code Duplication |
foreach(isset($extras[0]) ? $extras : array($extras) as $extra) { |
415
|
|
|
if ($extra['data'] && is_array($extra['data'])) { |
416
|
|
|
$content = array_merge($content, $extra['data']); |
417
|
|
|
} |
418
|
|
|
} |
419
|
|
|
} |
420
|
|
|
} |
421
|
|
|
unset($hook_data); |
422
|
|
|
|
423
|
|
|
if (isset($GLOBALS['egw_info']['flags']['java_script'])) |
424
|
|
|
{ |
425
|
|
|
// Strip out any script tags |
426
|
|
|
$GLOBALS['egw_info']['flags']['java_script'] = preg_replace(array('/(<script[^>]*>)([^<]*)/is','/<\/script>/'),array('$2',''),$GLOBALS['egw_info']['flags']['java_script']); |
427
|
|
|
self::$response->script($GLOBALS['egw_info']['flags']['java_script']); |
|
|
|
|
428
|
|
|
//error_log($app .' added javascript to $GLOBALS[egw_info][flags][java_script] - use Json\Response->script() instead.'); |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
return $content; |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
/** |
435
|
|
|
* Notify server that eT session/request is no longer needed, because user closed window |
436
|
|
|
* |
437
|
|
|
* @param string $_exec_id |
438
|
|
|
*/ |
439
|
|
|
static public function ajax_destroy_session($_exec_id) |
440
|
|
|
{ |
441
|
|
|
//error_log(__METHOD__."('$_exec_id')"); |
442
|
|
|
if (($request = Etemplate\Request::read($_exec_id))) |
443
|
|
|
{ |
444
|
|
|
$request->remove_if_not_modified(); |
445
|
|
|
unset($request); |
446
|
|
|
} |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
/** |
450
|
|
|
* Process via POST submitted content |
451
|
|
|
*/ |
452
|
|
|
static public function process_exec() |
453
|
|
|
{ |
454
|
|
|
if (get_magic_quotes_gpc()) $_POST['value'] = stripslashes($_POST['value']); |
455
|
|
|
$content = json_decode($_POST['value'],true); |
456
|
|
|
//error_log(__METHOD__."(".array2string($content).")"); |
457
|
|
|
|
458
|
|
|
self::$request = Etemplate\Request::read($_POST['etemplate_exec_id']); |
459
|
|
|
|
460
|
|
View Code Duplication |
if (!($template = self::instance(self::$request->template['name'], self::$request->template['template_set'], |
461
|
|
|
self::$request->template['version'], self::$request->template['load_via']))) |
462
|
|
|
{ |
463
|
|
|
throw new Exception\WrongParameter('Can NOT read template '.array2string(self::$request->template)); |
464
|
|
|
} |
465
|
|
|
$validated = array(); |
466
|
|
|
$expand = array( |
467
|
|
|
'cont' => &self::$request->content, |
468
|
|
|
); |
469
|
|
|
$template->run('validate', array('', $expand, $content, &$validated), true); // $respect_disabled=true: do NOT validate disabled widgets and children |
470
|
|
|
if (self::validation_errors(self::$request->ignore_validation)) |
471
|
|
|
{ |
472
|
|
|
error_log(__METHOD__."(,".array2string($content).') validation_errors='.array2string(self::$validation_errors)); |
473
|
|
|
exit; |
474
|
|
|
} |
475
|
|
|
//error_log(__METHOD__."(,".array2string($content).')'); |
476
|
|
|
//error_log(' validated='.array2string($validated)); |
477
|
|
|
|
478
|
|
|
return ExecMethod(self::$request->method, self::complete_array_merge(self::$request->preserv, $validated)); |
|
|
|
|
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
public $name; |
482
|
|
|
public $template_set; |
483
|
|
|
public $version; |
484
|
|
|
public $laod_via; |
485
|
|
|
|
486
|
|
|
/** |
487
|
|
|
* |
488
|
|
|
* @var string If the template needs a div named other than the template name, this is it |
489
|
|
|
*/ |
490
|
|
|
protected $dom_id; |
491
|
|
|
|
492
|
|
|
/** |
493
|
|
|
* Reads an eTemplate from filesystem or DB (not yet supported) |
494
|
|
|
* |
495
|
|
|
* @param string $name name of the eTemplate or array with the values for all keys |
496
|
|
|
* @param string $template_set =null default try template-set from user and if not found "default" |
497
|
|
|
* @param string $lang language, '' loads the pref. lang of the user, 'default' loads the default one '' in the db |
498
|
|
|
* @param int $group id of the (primary) group of the user or 0 for none, not used at the moment !!! |
499
|
|
|
* @param string $version version of the eTemplate |
500
|
|
|
* @param mixed $load_via name/array of keys of etemplate to load in order to get $name (only as second try!) |
501
|
|
|
* @return boolean True if a fitting template is found, else False |
502
|
|
|
* |
503
|
|
|
* @ToDo supported customized templates stored in DB |
504
|
|
|
*/ |
505
|
|
|
public function read($name,$template_set=null,$lang='default',$group=0,$version='',$load_via='') |
506
|
|
|
{ |
507
|
|
|
|
508
|
|
|
// For mobile experience try to load custom mobile templates |
509
|
|
|
if (Header\UserAgent::mobile()) |
510
|
|
|
{ |
511
|
|
|
$template_set = "mobile"; |
512
|
|
|
} |
513
|
|
|
|
514
|
|
|
unset($lang); unset($group); // not used, but in old signature |
515
|
|
|
$this->rel_path = self::relPath($this->name=$name, $this->template_set=$template_set, |
516
|
|
|
$this->version=$version, $this->laod_via = $load_via); |
|
|
|
|
517
|
|
|
//error_log(__METHOD__."('$name', '$template_set', '$lang', $group, '$version', '$load_via') rel_path=".array2string($this->rel_path)); |
518
|
|
|
|
519
|
|
|
$this->dom_id = $name; |
520
|
|
|
|
521
|
|
|
return (boolean)$this->rel_path; |
522
|
|
|
} |
523
|
|
|
|
524
|
|
|
/** |
525
|
|
|
* Set the DOM ID for the etemplate div. If not set, it will be generated from the template name. |
526
|
|
|
* |
527
|
|
|
* @param string $new_id |
528
|
|
|
*/ |
529
|
|
|
public function set_dom_id($new_id) |
530
|
|
|
{ |
531
|
|
|
$this->dom_id = $new_id; |
532
|
|
|
} |
533
|
|
|
|
534
|
|
|
/** |
535
|
|
|
* Make sure there's a new request, in case of multiple Etemplates in one call. |
536
|
|
|
* Normally this isn't a problem, but if you've got an etemplate in the sidebox, |
537
|
|
|
* and are seeing problems submitting another etemplate, try this before executing |
538
|
|
|
* the sidebox etemplate. |
539
|
|
|
*/ |
540
|
|
|
public static function reset_request() |
541
|
|
|
{ |
542
|
|
|
self::$request = Etemplate\Request::read(); |
543
|
|
|
self::$cache = array(); |
544
|
|
|
} |
545
|
|
|
/** |
546
|
|
|
* Get template data as array |
547
|
|
|
* |
548
|
|
|
* @return array |
549
|
|
|
*/ |
550
|
|
|
public function as_array() |
551
|
|
|
{ |
552
|
|
|
return array( |
553
|
|
|
'name' => $this->name, |
554
|
|
|
'template_set' => $this->template_set, |
555
|
|
|
'version' => $this->version, |
556
|
|
|
'load_via' => $this->load_via, |
557
|
|
|
); |
558
|
|
|
} |
559
|
|
|
|
560
|
|
|
/** |
561
|
|
|
* Returns reference to an attribute in a named cell |
562
|
|
|
* |
563
|
|
|
* Currently we always return a reference to an not set value, unless it was set before. |
564
|
|
|
* We do not return a reference to the actual cell, as it get's contructed on client-side! |
565
|
|
|
* |
566
|
|
|
* @param string $name cell-name |
567
|
|
|
* @param string $attr attribute-name |
568
|
|
|
* @return mixed reference to attribute, usually NULL |
569
|
|
|
* @deprecated use getElementAttribute($name, $attr) |
570
|
|
|
*/ |
571
|
|
|
public function &get_cell_attribute($name,$attr) |
572
|
|
|
{ |
573
|
|
|
return self::getElementAttribute($name, $attr); |
574
|
|
|
} |
575
|
|
|
|
576
|
|
|
/** |
577
|
|
|
* set an attribute in a named cell if val is not NULL else return the attribute |
578
|
|
|
* |
579
|
|
|
* @param string $name cell-name |
580
|
|
|
* @param string $attr attribute-name |
581
|
|
|
* @param mixed $val if not NULL sets attribute else returns it |
582
|
|
|
* @return reference to attribute |
583
|
|
|
* @deprecated use setElementAttribute($name, $attr, $val) |
584
|
|
|
*/ |
585
|
|
|
public function &set_cell_attribute($name,$attr,$val) |
586
|
|
|
{ |
587
|
|
|
return self::setElementAttribute($name, $attr, $val); |
588
|
|
|
} |
589
|
|
|
|
590
|
|
|
/** |
591
|
|
|
* disables all cells with name == $name |
592
|
|
|
* |
593
|
|
|
* @param sting $name cell-name |
594
|
|
|
* @param boolean $disabled =true disable or enable a cell, default true=disable |
595
|
|
|
* @return reference to attribute |
596
|
|
|
* @deprecated use disableElement($name, $disabled=true) |
597
|
|
|
*/ |
598
|
|
|
public function disable_cells($name,$disabled=True) |
599
|
|
|
{ |
600
|
|
|
return self::disableElement($name, $disabled); |
601
|
|
|
} |
602
|
|
|
|
603
|
|
|
/** |
604
|
|
|
* merges $old and $new, content of $new has precedence over $old |
605
|
|
|
* |
606
|
|
|
* THIS IS NOT THE SAME AS PHP's functions: |
607
|
|
|
* - array_merge, as it calls itself recursive for values which are arrays. |
608
|
|
|
* - array_merge_recursive accumulates values with the same index and $new does NOT overwrite $old |
609
|
|
|
* |
610
|
|
|
* @param array $old |
611
|
|
|
* @param array $new |
612
|
|
|
* @return array the merged array |
613
|
|
|
*/ |
614
|
|
|
public static function complete_array_merge($old,$new) |
615
|
|
|
{ |
616
|
|
|
if (is_array($new)) |
617
|
|
|
{ |
618
|
|
|
if (!is_array($old)) $old = (array) $old; |
619
|
|
|
|
620
|
|
|
foreach($new as $k => $v) |
621
|
|
|
{ |
622
|
|
|
if (!is_array($v) || !isset($old[$k]) || // no array or a new array |
623
|
|
|
isset($v[0]) && !is_array($v[0]) && isset($v[count($v)-1]) || // or no associative array, eg. selecting multiple accounts |
624
|
|
|
is_array($v) && count($v) == 0) // Empty array replacing non-empty |
625
|
|
|
{ |
626
|
|
|
$old[$k] = $v; |
627
|
|
|
} |
628
|
|
|
else |
629
|
|
|
{ |
630
|
|
|
$old[$k] = self::complete_array_merge($old[$k],$v); |
631
|
|
|
} |
632
|
|
|
} |
633
|
|
|
} |
634
|
|
|
return $old; |
635
|
|
|
} |
636
|
|
|
|
637
|
|
|
/** |
638
|
|
|
* Deep copy array to make sure there are no references |
639
|
|
|
* |
640
|
|
|
* @param Array $array |
641
|
|
|
* @return Array |
642
|
|
|
*/ |
643
|
|
|
public static function deep_copy($source) |
644
|
|
|
{ |
645
|
|
|
$arr = array(); |
646
|
|
|
|
647
|
|
|
foreach ($source as $key => $element) |
648
|
|
|
{ |
649
|
|
|
if (is_array($element)) |
650
|
|
|
{ |
651
|
|
|
$arr[$key] = static::deep_copy($element); |
652
|
|
|
} |
653
|
|
|
else if (is_object($element)) |
654
|
|
|
{ |
655
|
|
|
// make an object copy |
656
|
|
|
$arr[$key] = clone $element; |
657
|
|
|
} |
658
|
|
|
else |
659
|
|
|
{ |
660
|
|
|
$arr[$key] = $element; |
661
|
|
|
} |
662
|
|
|
} |
663
|
|
|
return $arr; |
664
|
|
|
} |
665
|
|
|
|
666
|
|
|
|
667
|
|
|
/** |
668
|
|
|
* Debug callback just outputting content |
669
|
|
|
* |
670
|
|
|
* @param array $content =null |
671
|
|
|
*/ |
672
|
|
|
public function debug(array $content=null) |
673
|
|
|
{ |
674
|
|
|
$GLOBALS['egw']->framework->render(print_r($content, true)); |
675
|
|
|
} |
676
|
|
|
|
677
|
|
|
/** |
678
|
|
|
* Message containing the max Upload size from the current php.ini settings |
679
|
|
|
* |
680
|
|
|
* We have to take the smaler one of upload_max_filesize AND post_max_size-2800 into account. |
681
|
|
|
* memory_limit does NOT matter any more, because of the stream-interface of the vfs. |
682
|
|
|
* |
683
|
|
|
* @param int &$max_upload=null on return max. upload size in byte |
684
|
|
|
* @return string |
685
|
|
|
*/ |
686
|
|
|
static function max_upload_size_message(&$max_upload=null) |
687
|
|
|
{ |
688
|
|
|
$upload_max_filesize = ini_get('upload_max_filesize'); |
689
|
|
|
$post_max_size = ini_get('post_max_size'); |
690
|
|
|
$max_upload = min(self::km2int($upload_max_filesize),self::km2int($post_max_size)-2800); |
691
|
|
|
|
692
|
|
|
return lang('Maximum size for uploads').': '.Vfs::hsize($max_upload). |
693
|
|
|
" (php.ini: upload_max_filesize=$upload_max_filesize, post_max_size=$post_max_size)"; |
694
|
|
|
} |
695
|
|
|
|
696
|
|
|
/** |
697
|
|
|
* Format a number according to user prefs with decimal and thousands separator (later only for readonly) |
698
|
|
|
* |
699
|
|
|
* @param int|float|string $number |
700
|
|
|
* @param int $num_decimal_places =2 |
701
|
|
|
* @param boolean $readonly =true |
702
|
|
|
* @return string |
703
|
|
|
*/ |
704
|
|
|
static public function number_format($number,$num_decimal_places=2,$readonly=true) |
705
|
|
|
{ |
706
|
|
|
static $dec_separator=null,$thousands_separator=null; |
707
|
|
View Code Duplication |
if (is_null($dec_separator)) |
708
|
|
|
{ |
709
|
|
|
$dec_separator = $GLOBALS['egw_info']['user']['preferences']['common']['number_format'][0]; |
710
|
|
|
if (empty($dec_separator)) $dec_separator = '.'; |
711
|
|
|
$thousands_separator = $GLOBALS['egw_info']['user']['preferences']['common']['number_format'][1]; |
712
|
|
|
} |
713
|
|
|
if ((string)$number === '') return ''; |
714
|
|
|
|
715
|
|
|
return number_format(str_replace(' ','',$number),$num_decimal_places,$dec_separator,$readonly ? $thousands_separator : ''); |
716
|
|
|
} |
717
|
|
|
|
718
|
|
|
/** |
719
|
|
|
* Convert numbers like '32M' or '512k' to integers |
720
|
|
|
* |
721
|
|
|
* @param string $size |
722
|
|
|
* @return int |
723
|
|
|
*/ |
724
|
|
|
private static function km2int($size) |
725
|
|
|
{ |
726
|
|
|
if (!is_numeric($size)) |
727
|
|
|
{ |
728
|
|
|
switch(strtolower(substr($size,-1))) |
729
|
|
|
{ |
730
|
|
|
case 'm': |
731
|
|
|
$size = 1024*1024*(int)$size; |
732
|
|
|
break; |
733
|
|
|
case 'k': |
734
|
|
|
$size = 1024*(int)$size; |
735
|
|
|
break; |
736
|
|
|
} |
737
|
|
|
} |
738
|
|
|
return (int)$size; |
739
|
|
|
} |
740
|
|
|
} |
741
|
|
|
|
This function has been deprecated. The supplier of the file has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.