Completed
Push — 17.1 ( e791a5...a2e62e )
by Ralf
40:37 queued 26:22
created

Etemplate::deep_copy()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 1
dl 0
loc 22
rs 9.568
c 0
b 0
f 0
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();
0 ignored issues
show
Deprecated Code introduced by
The function parse_navbar() has been deprecated with message: use $GLOBALS['egw']->framework->navbar() or $GLOBALS['egw']->framework::render()

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.

Loading history...
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));
0 ignored issues
show
Deprecated Code introduced by
The function ExecMethod() has been deprecated with message: use autoloadable class-names, instanciate and call method or use static methods

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.

Loading history...
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']);
0 ignored issues
show
Deprecated Code introduced by
The method EGroupware\Api\Json\Msg::script() has been deprecated.

This method has been deprecated.

Loading history...
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));
0 ignored issues
show
Deprecated Code introduced by
The function ExecMethod() has been deprecated with message: use autoloadable class-names, instanciate and call method or use static methods

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.

Loading history...
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);
0 ignored issues
show
Unused Code introduced by
The call to Etemplate::relPath() has too many arguments starting with $this->laod_via = $load_via.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
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