Completed
Push — 16.1 ( 73e4a9...4f61bc )
by Nathan
15:20
created

Etemplate::disable_cells()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
rs 10
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
		}
134
		unset($hook_data);
135
136
		// Include the etemplate2 javascript code
137
		Framework::includeJS('etemplate', 'etemplate2', 'api');
138
139
		if (!$this->rel_path) throw new Exception\AssertionFailed("No (valid) template '$this->name' found!");
140
141
		if ($output_mode == 4)
142
		{
143
			$output_mode = 0;
144
			self::$response = Json\Response::get();
145
		}
146
		self::$request->output_mode = $output_mode;	// let extensions "know" they are run eg. in a popup
147
		self::$request->content = self::$cont = $content;
148
		self::$request->changes = $changes;
149
		self::$request->sel_options = is_array($sel_options) ? self::fix_sel_options($sel_options) : array();
150
		self::$request->readonlys = $readonlys ? $readonlys : array();
151
		self::$request->preserv = $preserv ? $preserv : array();
152
		self::$request->method = $method;
153
		self::$request->ignore_validation = $ignore_validation;
154
		if (self::$request->output_mode == -1) self::$request->output_mode = 0;
155
		self::$request->template = $this->as_array();
156
157
		if (empty($this->name)) throw new Exception\AssertionFailed("Template  name is not set '$this->name' !");
158
		// instanciate template to fill self::$request->sel_options for select-* widgets
159
		// 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
160
		$template = self::instance($this->name, $this->template_set, $this->version, $this->laod_via);
161
		if (!$template) throw new Exception\AssertionFailed("Template $this->name not instanciable! Maybe you forgot to rename template id.");
162
		$template->run('beforeSendToClient', array('', array('cont'=>$content)));
163
164
		// some apps (eg. InfoLog) set app_header only in get_rows depending on filter settings
165
		self::$request->app_header = $GLOBALS['egw_info']['flags']['app_header'];
166
167
		// compile required translations translations
168
		$currentapp = $GLOBALS['egw_info']['flags']['currentapp'];
169
		$langRequire = array('common' => array(), 'etemplate' => array());	// keep that order
170
		foreach(Translation::$loaded_apps as $l_app => $lang)
171
		{
172
			if (!in_array($l_app, array($currentapp, 'custom')))
173
			{
174
				$langRequire[$l_app] = array('app' => $l_app, 'lang' => $lang, 'etag' => Translation::etag($l_app, $lang));
175
			}
176
		}
177
		foreach(array($currentapp, 'custom') as $l_app)
178
		{
179
			if (isset(Translation::$loaded_apps[$l_app]))
180
			{
181
				$langRequire[$l_app] = array('app' => $l_app, 'lang' => Translation::$loaded_apps[$l_app], 'etag' => Translation::etag($l_app, Translation::$loaded_apps[$l_app]));
182
			}
183
		}
184
185
		$data = array(
186
			'etemplate_exec_id' => self::$request->id(),
187
			'app_header' => self::$request->app_header,
188
			'content' => self::$request->content,
189
			'sel_options' => self::$request->sel_options,
190
			'readonlys' => self::$request->readonlys,
191
			'modifications' => self::$request->modifications,
192
			'validation_errors' => self::$validation_errors,
193
			'langRequire' => array_values($langRequire),
194
			'currentapp' => $currentapp,
195
		);
196
197
		// Info required to load the etemplate client-side
198
		$dom_id = str_replace('.','-',$this->dom_id);
199
		$load_array = array(
200
			'name' => $this->name,
201
			'url' => self::rel2url($this->rel_path),
202
			'data' => $data,
203
			'DOMNodeID' => $dom_id,
204
		);
205
		if (self::$response)	// call is within an ajax event / form submit
206
		{
207
			//error_log("Ajax " . __LINE__);
208
			self::$response->generic('et2_load', $load_array+Framework::get_extra());
209
			Framework::clear_extra();	// to not send/set it twice for multiple etemplates (eg. CRM view)
210
		}
211
		else	// first call
212
		{
213
			// check if application of template has a app.js file --> load it
214
			list($app) = explode('.',$this->name);
215
			if (file_exists(EGW_SERVER_ROOT.'/'.$app.'/js/app.js'))
216
			{
217
				Framework::includeJS('.','app',$app,false);
0 ignored issues
show
Unused Code introduced by
The call to Framework::includeJS() has too many arguments starting with false.

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...
218
			}
219
			// Category styles
220
			Categories::css($app);
221
222
			// set action attribute for autocomplete form tag
223
			// as firefox complains on about:balnk action, thus we have to literaly submit the form to a blank html
224
			$form_action = "about:blank";
225
			if (in_array(Header\UserAgent::type(), array('firefox', 'safari')))
226
			{
227
				$form_action = $GLOBALS['egw_info']['server']['webserver_url'].'/api/templates/default/empty.html';
228
			}
229
			// check if we are in an ajax-exec call from jdots template (or future other tabbed templates)
230
			if (isset($GLOBALS['egw']->framework->response))
231
			{
232
				$content = '<form target="egw_iframe_autocomplete_helper" action="'.$form_action.'" id="'.$dom_id.'" class="et2_container"></form>'."\n".
233
					'<iframe name="egw_iframe_autocomplete_helper" style="width:0;height:0;position: absolute;visibility:hidden;"></iframe>';
234
				$GLOBALS['egw']->framework->response->generic("data", array($content));
235
				$GLOBALS['egw']->framework->response->generic('et2_load',$load_array+Framework::get_extra());
236
				Framework::clear_extra();	// to not send/set it twice for multiple etemplates (eg. CRM view)
237
				self::$request = null;
238
				return;
239
			}
240
			// let framework know, if we are a popup or not ('popup' not true, which is allways used by index.php!)
241
			if (!isset($GLOBALS['egw_info']['flags']['nonavbar']) || is_bool($GLOBALS['egw_info']['flags']['nonavbar']))
242
			{
243
				$GLOBALS['egw_info']['flags']['nonavbar'] = $output_mode == 2 ? 'popup' : false;
244
			}
245
			echo $GLOBALS['egw']->framework->header();
246
			if ($output_mode != 2 && !$GLOBALS['egw_info']['flags']['nonavbar'])
247
			{
248
				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...
249
			}
250
			else	// mark popups as such, by enclosing everything in div#popupMainDiv
251
			{
252
				echo '<div id="popupMainDiv" class="popupMainDiv">'."\n";
253
			}
254
			// Send any accumulated json responses - after flush to avoid sending the buffer as a response
255
			if(Json\Response::isJSONResponse())
256
			{
257
				$load_array['response'] = Json\Response::get()->returnResult();
258
			}
259
			// <iframe> and <form> tags added only to get browser autocomplete handling working again
260
			$form = '<form target="egw_iframe_autocomplete_helper" action="'.$form_action.'" id="'.$dom_id.'" class="et2_container" data-etemplate="'.
261
				htmlspecialchars(Json\Response::json_encode($load_array), ENT_COMPAT, Translation::charset(), true).'"></form>'."\n".
262
				'<iframe name="egw_iframe_autocomplete_helper" style="width:0;height:0;position: absolute;visibility:hidden;"></iframe>';
263
			echo $form;
264
265
			if ($output_mode == 2)
266
			{
267
				echo "\n</div>\n";
268
				echo $GLOBALS['egw']->framework->footer();
269
			}
270
			ob_flush();
271
			if ($output_mode == 1)	return $form;
272
		}
273
		self::$request = null;
274
	}
275
276
	/**
277
	 * Fix all sel_options, as Etemplate\Widget\Select::beforeSendToClient is not run for auto-repeated stuff not understood by server
278
	 *
279
	 * @param array $sel_options
280
	 * @return array
281
	 */
282
	static protected function fix_sel_options(array $sel_options)
283
	{
284
		foreach($sel_options as &$options)
285
		{
286
			if (!is_array($options)||empty($options)) continue;
287
			foreach($options as $key => $value)
288
			{
289
				if (is_numeric($key) && (!is_array($value) || !isset($value['value'])))
290
				{
291
					Etemplate\Widget\Select::fix_encoded_options($options, true);
292
					break;
293
				}
294
			}
295
		}
296
		return $sel_options;
297
	}
298
299
	/**
300
	 * Process via Ajax submitted content
301
	 *
302
	 * @param string $etemplate_exec_id
303
	 * @param array $_content
304
	 * @param boolean $no_validation
305
	 * @throws Exception\WrongParameter
306
	 */
307
	static public function ajax_process_content($etemplate_exec_id, array $_content, $no_validation)
308
	{
309
		//error_log(__METHOD__."(".array2string($etemplate_exec_id).', '.array2string($_content).")");
310
311
		self::$request = Etemplate\Request::read($etemplate_exec_id);
312
		//error_log('request='.array2string(self::$request));
313
314
		self::$response = Json\Response::get();
315
316 View Code Duplication
		if (!($template = self::instance(self::$request->template['name'], self::$request->template['template_set'],
317
			self::$request->template['version'], self::$request->template['load_via'])))
318
		{
319
			throw new Exception\WrongParameter('Can NOT read template '.array2string(self::$request->template));
320
		}
321
322
		// let session class know, which is the app & method of this request
323
		$GLOBALS['egw']->session->set_action('Etemplate: '.self::$request->method);
324
325
		// Set current app for validation
326
		list($app) = explode('.',self::$request->method);
327
		if(!$app) list($app) = explode('::',self::$request->method);
328
		if($app)
329
		{
330
			Translation::add_app($app);
331
			$GLOBALS['egw_info']['flags']['currentapp'] = $app;
332
		}
333
		$validated = array();
334
		$expand = array(
335
			'cont' => &self::$request->content,
336
		);
337
		$template->run('validate', array('', $expand, $_content, &$validated), true);	// $respect_disabled=true: do NOT validate disabled widgets and children
338
339
		if ($no_validation)
340
		{
341
			self::$validation_errors = array();
342
		}
343
		elseif (self::validation_errors(self::$request->ignore_validation))
344
		{
345
			error_log(__METHOD__."(,".array2string($_content).') validation_errors='.array2string(self::$validation_errors));
346
			self::$response->generic('et2_validation_error', self::$validation_errors);
347
			exit;
348
		}
349
350
		// tell request call to remove request, if it is not modified eg. by call to exec in callback
351
		self::$request->remove_if_not_modified();
352
353
		foreach(Hooks::process(array(
354
			'hook_location'   => 'etemplate2_before_process',
355
			'location_name'   => $template->id,
356
		) + self::complete_array_merge(self::$request->preserv, $validated)) as $extras)
357
		{
358
			if (!$extras) continue;
359
360 View Code Duplication
			foreach(isset($extras[0]) ? $extras : array($extras) as $extra)
361
			{
362
				if ($extra['data'] && is_array($extra['data']))
363
				{
364
					$validated = array_merge($validated, $extra['data']);
365
				}
366
			}
367
		}
368
369
		//error_log(__METHOD__."(,".array2string($content).')');
370
		//error_log(' validated='.array2string($validated));
371
		$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...
372
373
		$tcontent = is_array($content) ? $content :
374
			self::complete_array_merge(self::$request->preserv, $validated);
375
376
		$hook_data = Hooks::process(
377
			array(
378
				'hook_location'   => 'etemplate2_after_process',
379
				'location_name'   => $template->id
380
			) + $tcontent);
381
382
		unset($tcontent);
383
384
		if (is_array($content))
385
		{
386
			foreach($hook_data as $extras)
387
			{
388
				if (!$extras) continue;
389
390 View Code Duplication
				foreach(isset($extras[0]) ? $extras : array($extras) as $extra) {
391
					if ($extra['data'] && is_array($extra['data'])) {
392
						$content = array_merge($content, $extra['data']);
393
					}
394
				}
395
			}
396
		}
397
		unset($hook_data);
398
399
		if (isset($GLOBALS['egw_info']['flags']['java_script']))
400
		{
401
			// Strip out any script tags
402
			$GLOBALS['egw_info']['flags']['java_script'] = preg_replace(array('/(<script[^>]*>)([^<]*)/is','/<\/script>/'),array('$2',''),$GLOBALS['egw_info']['flags']['java_script']);
403
			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...
404
			//error_log($app .' added javascript to $GLOBALS[egw_info][flags][java_script] - use Json\Response->script() instead.');
405
		}
406
407
		return $content;
408
	}
409
410
	/**
411
	 * Notify server that eT session/request is no longer needed, because user closed window
412
	 *
413
	 * @param string $_exec_id
414
	 */
415
	static public function ajax_destroy_session($_exec_id)
416
	{
417
		//error_log(__METHOD__."('$_exec_id')");
418
		if (($request = Etemplate\Request::read($_exec_id)))
419
		{
420
			$request->remove_if_not_modified();
421
			unset($request);
422
		}
423
	}
424
425
	/**
426
	 * Process via POST submitted content
427
	 */
428
	static public function process_exec()
429
	{
430
		if (get_magic_quotes_gpc()) $_POST['value'] = stripslashes($_POST['value']);
431
		$content = json_decode($_POST['value'],true);
432
		//error_log(__METHOD__."(".array2string($content).")");
433
434
		self::$request = Etemplate\Request::read($_POST['etemplate_exec_id']);
435
436 View Code Duplication
		if (!($template = self::instance(self::$request->template['name'], self::$request->template['template_set'],
437
			self::$request->template['version'], self::$request->template['load_via'])))
438
		{
439
			throw new Exception\WrongParameter('Can NOT read template '.array2string(self::$request->template));
440
		}
441
		$validated = array();
442
		$expand = array(
443
			'cont' => &self::$request->content,
444
		);
445
		$template->run('validate', array('', $expand, $content, &$validated), true);	// $respect_disabled=true: do NOT validate disabled widgets and children
446
		if (self::validation_errors(self::$request->ignore_validation))
447
		{
448
			error_log(__METHOD__."(,".array2string($content).') validation_errors='.array2string(self::$validation_errors));
449
			exit;
450
		}
451
		//error_log(__METHOD__."(,".array2string($content).')');
452
		//error_log(' validated='.array2string($validated));
453
454
		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...
455
	}
456
457
	public $name;
458
	public $template_set;
459
	public $version;
460
	public $laod_via;
461
462
	/**
463
	 *
464
	 * @var string If the template needs a div named other than the template name, this is it
465
	 */
466
	protected $dom_id;
467
468
	/**
469
	 * Reads an eTemplate from filesystem or DB (not yet supported)
470
	 *
471
	 * @param string $name name of the eTemplate or array with the values for all keys
472
	 * @param string $template_set =null default try template-set from user and if not found "default"
473
	 * @param string $lang language, '' loads the pref. lang of the user, 'default' loads the default one '' in the db
474
	 * @param int $group id of the (primary) group of the user or 0 for none, not used at the moment !!!
475
	 * @param string $version version of the eTemplate
476
	 * @param mixed $load_via name/array of keys of etemplate to load in order to get $name (only as second try!)
477
	 * @return boolean True if a fitting template is found, else False
478
	 *
479
	 * @ToDo supported customized templates stored in DB
480
	 */
481
	public function read($name,$template_set=null,$lang='default',$group=0,$version='',$load_via='')
482
	{
483
484
		// For mobile experience try to load custom mobile templates
485
		if (Header\UserAgent::mobile())
486
		{
487
			$template_set = "mobile";
488
		}
489
490
		unset($lang); unset($group);	// not used, but in old signature
491
		$this->rel_path = self::relPath($this->name=$name, $this->template_set=$template_set,
492
			$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...
493
		//error_log(__METHOD__."('$name', '$template_set', '$lang', $group, '$version', '$load_via') rel_path=".array2string($this->rel_path));
494
495
		$this->dom_id = $name;
496
497
		return (boolean)$this->rel_path;
498
	}
499
500
	/**
501
	 * Set the DOM ID for the etemplate div.  If not set, it will be generated from the template name.
502
	 *
503
	 * @param string $new_id
504
	 */
505
	public function set_dom_id($new_id)
506
	{
507
		$this->dom_id = $new_id;
508
	}
509
	
510
	/**
511
	 * Make sure there's a new request, in case of multiple Etemplates in one call.
512
	 * Normally this isn't a problem, but if you've got an etemplate in the sidebox,
513
	 * and are seeing problems submitting another etemplate, try this before executing
514
	 * the sidebox etemplate.
515
	 */
516
	public static function reset_request()
517
	{
518
		self::$request = Etemplate\Request::read();
519
	}
520
	/**
521
	 * Get template data as array
522
	 *
523
	 * @return array
524
	 */
525
	public function as_array()
526
	{
527
		return array(
528
			'name' => $this->name,
529
			'template_set' => $this->template_set,
530
			'version' => $this->version,
531
			'load_via' => $this->load_via,
532
		);
533
	}
534
535
	/**
536
	 * Returns reference to an attribute in a named cell
537
	 *
538
	 * Currently we always return a reference to an not set value, unless it was set before.
539
	 * We do not return a reference to the actual cell, as it get's contructed on client-side!
540
	 *
541
	 * @param string $name cell-name
542
	 * @param string $attr attribute-name
543
	 * @return mixed reference to attribute, usually NULL
544
	 * @deprecated use getElementAttribute($name, $attr)
545
	 */
546
	public function &get_cell_attribute($name,$attr)
547
	{
548
		return self::getElementAttribute($name, $attr);
549
	}
550
551
	/**
552
	 * set an attribute in a named cell if val is not NULL else return the attribute
553
	 *
554
	 * @param string $name cell-name
555
	 * @param string $attr attribute-name
556
	 * @param mixed $val if not NULL sets attribute else returns it
557
	 * @return reference to attribute
558
	 * @deprecated use setElementAttribute($name, $attr, $val)
559
	 */
560
	public function &set_cell_attribute($name,$attr,$val)
561
	{
562
		return self::setElementAttribute($name, $attr, $val);
563
	}
564
565
	/**
566
	 *  disables all cells with name == $name
567
	 *
568
	 * @param sting $name cell-name
569
	 * @param boolean $disabled =true disable or enable a cell, default true=disable
570
	 * @return reference to attribute
571
	 * @deprecated use disableElement($name, $disabled=true)
572
	 */
573
	public function disable_cells($name,$disabled=True)
574
	{
575
		return self::disableElement($name, $disabled);
576
	}
577
578
	/**
579
	 * merges $old and $new, content of $new has precedence over $old
580
	 *
581
	 * THIS IS NOT THE SAME AS PHP's functions:
582
	 * - array_merge, as it calls itself recursive for values which are arrays.
583
	 * - array_merge_recursive accumulates values with the same index and $new does NOT overwrite $old
584
	 *
585
	 * @param array $old
586
	 * @param array $new
587
	 * @return array the merged array
588
	 */
589
	public static function complete_array_merge($old,$new)
590
	{
591
		if (is_array($new))
592
		{
593
			if (!is_array($old)) $old = (array) $old;
594
595
			foreach($new as $k => $v)
596
			{
597
				if (!is_array($v) || !isset($old[$k]) || 	// no array or a new array
598
					isset($v[0]) && !is_array($v[0]) && isset($v[count($v)-1])	|| // or no associative array, eg. selecting multiple accounts
599
					is_array($v) && count($v) == 0)			// Empty array replacing non-empty
600
				{
601
					$old[$k] = $v;
602
				}
603
				else
604
				{
605
					$old[$k] = self::complete_array_merge($old[$k],$v);
606
				}
607
			}
608
		}
609
		return $old;
610
	}
611
612
	/**
613
	 * Debug callback just outputting content
614
	 *
615
	 * @param array $content =null
616
	 */
617
	public function debug(array $content=null)
618
	{
619
		$GLOBALS['egw']->framework->render(print_r($content, true));
620
	}
621
622
	/**
623
	 * Message containing the max Upload size from the current php.ini settings
624
	 *
625
	 * We have to take the smaler one of upload_max_filesize AND post_max_size-2800 into account.
626
	 * memory_limit does NOT matter any more, because of the stream-interface of the vfs.
627
	 *
628
	 * @param int &$max_upload=null on return max. upload size in byte
629
	 * @return string
630
	 */
631
	static function max_upload_size_message(&$max_upload=null)
632
	{
633
		$upload_max_filesize = ini_get('upload_max_filesize');
634
		$post_max_size = ini_get('post_max_size');
635
		$max_upload = min(self::km2int($upload_max_filesize),self::km2int($post_max_size)-2800);
636
637
		return lang('Maximum size for uploads').': '.Vfs::hsize($max_upload).
638
			" (php.ini: upload_max_filesize=$upload_max_filesize, post_max_size=$post_max_size)";
639
	}
640
641
	/**
642
	 * Format a number according to user prefs with decimal and thousands separator (later only for readonly)
643
	 *
644
	 * @param int|float|string $number
645
	 * @param int $num_decimal_places =2
646
	 * @param boolean $readonly =true
647
	 * @return string
648
	 */
649
	static public function number_format($number,$num_decimal_places=2,$readonly=true)
650
	{
651
		static $dec_separator=null,$thousands_separator=null;
652 View Code Duplication
		if (is_null($dec_separator))
653
		{
654
			$dec_separator = $GLOBALS['egw_info']['user']['preferences']['common']['number_format'][0];
655
			if (empty($dec_separator)) $dec_separator = '.';
656
			$thousands_separator = $GLOBALS['egw_info']['user']['preferences']['common']['number_format'][1];
657
		}
658
		if ((string)$number === '') return '';
659
660
		return number_format(str_replace(' ','',$number),$num_decimal_places,$dec_separator,$readonly ? $thousands_separator : '');
661
	}
662
663
	/**
664
	 * Convert numbers like '32M' or '512k' to integers
665
	 *
666
	 * @param string $size
667
	 * @return int
668
	 */
669
	private static function km2int($size)
670
	{
671
		if (!is_numeric($size))
672
		{
673
			switch(strtolower(substr($size,-1)))
674
			{
675
				case 'm':
676
					$size = 1024*1024*(int)$size;
677
					break;
678
				case 'k':
679
					$size = 1024*(int)$size;
680
					break;
681
			}
682
		}
683
		return (int)$size;
684
	}
685
}
686