resources_bo::link_title()   A
last analyzed

Complexity

Conditions 5
Paths 6

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 5
nc 6
nop 1
dl 0
loc 11
rs 9.6111
c 0
b 0
f 0
1
<?php
2
/**
3
 * EGroupware - resources
4
 *
5
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
6
 * @package resources
7
 * @link http://www.egroupware.org
8
 * @author Cornelius Weiss <[email protected]>
9
 * @author Lukas Weiss <[email protected]>
10
 * @version $Id$
11
 */
12
13
use EGroupware\Api;
14
use EGroupware\Api\Link;
15
use EGroupware\Api\Egw;
16
use EGroupware\Api\Acl;
17
use EGroupware\Api\Vfs;
18
19
/**
20
 * General business object for resources
21
 *
22
 * @package resources
23
 */
24
class resources_bo
25
{
26
	/**
27
	 * Path where the icons are stored (relative to webserver_url)
28
	 */
29
	const ICON_PATH = '/api/images';
30
31
	const DELETED = 'deleted';
32
	const PICTURE_NAME = '.picture.jpg';
33
	var $resource_icons = '/resources/templates/default/images/resource_icons/';
34
	var $debug = 0;
35
	/**
36
	 * Instance of resources so object
37
	 *
38
	 * @var resources_so
39
	 */
40
	var $so;
41
	/**
42
	 * Instance of resources Acl class
43
	 *
44
	 * @var resources_acl_bo
45
	 */
46
	var $acl;
47
	/**
48
	 * Instance of Api\Categories class for resources
49
	 */
50
	var $cats;
51
52
	/**
53
	 * List of filter options
54
	 */
55
	public static $filter_options = array(
56
		-1 => 'resources',
57
		-2 => 'accessories',
58
		-3 => 'resources and accessories'
59
		// Accessories of a resource added when resource selected
60
	);
61
62
	public static $field2label = array(
63
		'res_id'	=> 'Resource ID',
64
		'name'		=> 'name',
65
		'short_description'	=> 'short description',
66
		'cat_id'	=> 'Category',
67
		'quantity'	=> 'Quantity',
68
		'useable'	=> 'Useable',
69
		'location'	=> 'Location',
70
		'storage_info'	=> 'Storage',
71
		'bookable'	=> 'Bookable',
72
		'buyable'	=> 'Buyable',
73
		'prize'		=> 'Prize',
74
		'long_description'	=> 'Long description',
75
		'inventory_number'	=> 'inventory number',
76
		'accessory_of'	=> 'Accessory of'
77
	);
78
79
	/**
80
	 * Constructor
81
	 *
82
	 * @param int $user=null account_id of user to use for Acl, default current user
83
	 */
84
	function __construct($user=null)
85
	{
86
		$this->so = new resources_so();
87
		$this->acl = new resources_acl_bo($user);
88
		$this->cats = $this->acl->egw_cats;
89
90
		$this->cal_right_transform = array(
0 ignored issues
show
Bug Best Practice introduced by
The property cal_right_transform does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
91
			resources_acl_bo::CAL_READ	=> Acl::READ,
92
			resources_acl_bo::DIRECT_BOOKING 	=> Acl::READ | Acl::ADD | Acl::EDIT | Acl::DELETE,
93
			resources_acl_bo::CAT_ADMIN 	=> Acl::READ | Acl::ADD | Acl::EDIT | Acl::DELETE,
94
		);
95
	}
96
97
	/**
98
	 * get rows for resources list
99
	 *
100
	 * Cornelius Weiss <[email protected]>
101
	 */
102
	function get_rows($query,&$rows,&$readonlys)
103
	{
104
		if(!$query['csv_export'])
105
		{
106
			Api\Cache::setSession('resources', 'index_nm', $query);
107
		}
108
		if ($query['store_state'])	// request to store state in session and filter in prefs?
109
		{
110
			Api\Cache::setSession('resources',$query['store_state'],$query);
111
			//echo "<p>".__METHOD__."() query[filter]=$query[filter], prefs[resources][filter]={$GLOBALS['egw_info']['user']['preferences']['resources']['filter']}</p>\n";
112
			if ($query['filter'] != $GLOBALS['egw_info']['user']['preferences']['resources']['filter'])
113
			{
114
				$GLOBALS['egw']->preferences->add('resources','filter',$query['filter'],'user');
115
				$GLOBALS['egw']->preferences->save_repository();
116
			}
117
		}
118
		if ($this->debug) _debug_array($query);
119
		$read_onlys = 'res_id,name,short_description,quantity,useable,bookable,buyable,cat_id,location,storage_info';
0 ignored issues
show
Unused Code introduced by
The assignment to $read_onlys is dead and can be removed.
Loading history...
120
121
		$GLOBALS['egw']->session->commit_session();
122
		$filter = array();
123
		$join = '';
124
		$extra_cols = array();
125
126
		// Sub-query to get the count of accessories
127
		$acc_join = "LEFT JOIN (SELECT accessory_of AS accessory_id, count(res_id) as acc_count FROM {$this->so->table_name} GROUP BY accessory_of) AS acc ON acc.accessory_id = {$this->so->table_name}.res_id ";
128
129
		switch($query['filter2'])
130
		{
131
			case -1:
132
				// Resources only
133
				$filter['accessory_of'] = -1;
134
				$join = $acc_join;
135
				$extra_cols[] = 'acc_count';
136
				break;
137
			case -2:
138
				// Accessories only
139
				$filter[] = 'accessory_of != -1';
140
				break;
141
			case -3:
142
				// All
143
				$join = $acc_join;
144
				$extra_cols[] = 'acc_count';
145
				break;
146
			case self::DELETED:
147
				$join = $acc_join;
148
				$extra_cols[] = 'acc_count';
149
				$filter[] = 'deleted IS NOT NULL';
150
				break;
151
			default:
152
				$filter['accessory_of'] = $query['filter2'];
153
		}
154
		if($query['filter2'] != self::DELETED)
155
		{
156
			$filter['deleted'] = null;
157
		}
158
159
		if ($query['filter'])
160
		{
161
			if (($children = $this->acl->get_cats(Acl::READ,$query['filter'])))
162
			{
163
				$filter['cat_id'] = array_keys($children);
164
				$filter['cat_id'][] = $query['filter'];
165
			}
166
			else
167
			{
168
				$filter['cat_id'] = $query['filter'];
169
			}
170
		}
171
		elseif (($readcats = $this->acl->get_cats(Acl::READ)))
172
		{
173
			$filter['cat_id'] = array_keys($readcats);
174
		}
175
		// if there is no catfilter -> this means you have no rights, so set the cat filter to null
176
		if (!isset($filter['cat_id']) || empty($filter['cat_id'])) {
177
			$filter['cat_id'] = NUll;
178
		}
179
180
		if ($query['show_bookable'])
181
		{
182
			$filter['bookable'] = true;
183
		}
184
		$order_by = $query['order'] ? $query['order'].' '. $query['sort'] : '';
0 ignored issues
show
Unused Code introduced by
The assignment to $order_by is dead and can be removed.
Loading history...
185
		$start = (int)$query['start'];
0 ignored issues
show
Unused Code introduced by
The assignment to $start is dead and can be removed.
Loading history...
186
187
		foreach ($filter as $k => $v) $query['col_filter'][$k] = $v;
188
		$this->so->get_rows($query, $rows, $readonlys, $join, false, false, $extra_cols);
189
		$nr = $this->so->total;
190
191
		// we are called to serve bookable resources (e.g. calendar-dialog)
192
		if($query['show_bookable'])
193
		{
194
			// This is somehow ugly, i know...
195
			foreach((array)$rows as $num => $resource)
196
			{
197
				$rows[$num]['default_qty'] = 1;
198
			}
199
			// we don't need all the following testing
200
			return $nr;
201
		}
202
203
		$config = Api\Config::read('resources');
204
		foreach($rows as $num => &$resource)
205
		{
206
			if (!$this->acl->is_permitted($resource['cat_id'],Acl::EDIT))
207
			{
208
				$readonlys["edit[$resource[res_id]]"] = true;
209
			}
210
			elseif($resource['deleted'])
211
			{
212
				$resource['class'] .= 'deleted ';
213
			}
214
			if (!$this->acl->is_permitted($resource['cat_id'],Acl::DELETE) ||
215
				($resource['deleted'] && !$GLOBALS['egw_info']['user']['apps']['admin'] && $config['history'] == 'history')
216
			)
217
			{
218
				$readonlys["delete[$resource[res_id]]"] = true;
219
				$resource['class'] .= 'no_delete ';
220
			}
221
			if ((!$this->acl->is_permitted($resource['cat_id'],Acl::ADD)) ||
222
				// Allow new accessory action when viewing accessories of a certain resource
223
				$query['filter2'] <= 0 && $resource['accessory_of'] != -1)
224
			{
225
				$readonlys["new_acc[$resource[res_id]]"] = true;
226
				$resource['class'] .= 'no_new_accessory ';
227
			}
228
			if (!$resource['bookable'])
229
			{
230
				$readonlys["bookable[$resource[res_id]]"] = true;
231
				$readonlys["calendar[$resource[res_id]]"] = true;
232
				$resource['class'] .= 'no_book ';
233
				$resource['class'] .= 'no_view_calendar ';
234
			}
235
			if(!$this->acl->is_permitted($resource['cat_id'],resources_acl_bo::CAL_READ))
236
			{
237
				$readonlys["calendar[$resource[res_id]]"] = true;
238
				$resource['class'] .= 'no_view_calendar ';
239
			}
240
			if (!$resource['buyable'])
241
			{
242
				$readonlys["buyable[$resource[res_id]]"] = true;
243
				$resource['class'] .= 'no_buy ';
244
			}
245
			$readonlys["view_acc[{$resource['res_id']}]"] = ($resource['acc_count'] == 0);
246
			$resource['class'] .= ($resource['accessory_of']==-1 ? 'resource ' : 'accessory ');
247
			if($resource['acc_count'])
248
			{
249
				$resource['class'] .= 'hasAccessories ';
250
				$accessories = $this->get_acc_list($resource['res_id'],$query['filter2']==self::DELETED);
251
				foreach($accessories as $acc_id => $acc_name)
252
				{
253
					$resource['accessories'][] = array('acc_id' => $acc_id, 'name' => $this->link_title($acc_id));
254
				}
255
			} elseif ($resource['accessory_of'] > 0) {
256
				$resource['accessory_of_label'] = $this->link_title($resource['accessory_of']);
257
			}
258
259
			if($resource['deleted'])
260
			{
261
				$rows[$num]['picture_thumb'] = 'deleted';
262
			}
263
			else
264
			{
265
				$rows[$num]['picture_thumb'] = $this->get_picture($resource, false);
266
				if ($rows[$num]['picture_src'] == 'own_src')
267
				{
268
					// VFS picture fullsize
269
					$rows[$num]['picture_original'] = 'webdav.php/apps/resources/'.$rows[$num]['res_id'].'/.picture.jpg';
270
				}
271
				else
272
				{
273
					// cat or generic icon fullsize
274
					$rows[$num]['picture_original'] = $this->get_picture($resource, true);
275
				}
276
			}
277
			$rows[$num]['admin'] = $this->acl->get_cat_admin($resource['cat_id']);
278
		}
279
280
		if(!Api\Storage\Customfields::get('resources'))
281
		{
282
			$rows['no_customfields'] = true;
283
		}
284
		return $nr;
285
	}
286
287
	/**
288
	 * reads a resource exept binary datas
289
	 *
290
	 * Cornelius Weiss <[email protected]>
291
	 * @param int $res_id resource id
292
	 * @return array with key => value or false if not found or allowed
293
	 */
294
	function read($res_id)
295
	{
296
		if (!($data = $this->so->read(array('res_id' => $res_id))))
297
		{
298
			return null;	// not found
299
		}
300
		if (!$this->acl->is_permitted($data['cat_id'],Acl::READ))
301
		{
302
			return false;	// permission denied
303
		}
304
		return $data;
305
	}
306
307
	/**
308
	 * saves a resource. pictures are saved in vfs
309
	 *
310
	 * Cornelius Weiss <[email protected]>
311
	 * @param array $resource array with key => value of all needed datas
312
	 * @return string msg if somthing went wrong; nothing if all right
313
	 */
314
	function save($resource)
315
	{
316
		if(!$this->acl->is_permitted($resource['cat_id'],Acl::EDIT))
317
		{
318
			return lang('You are not permitted to edit this resource!');
319
		}
320
		$old = array();
321
		// we need an id to save pictures and make links...
322
		if(!$resource['res_id'])
323
		{
324
			$resource['res_creator'] = $GLOBALS['egw_info']['user']['account_id'];
325
			$resource['res_created'] = Api\DateTime::server2user(time(),'ts');
326
			$resource['res_id'] = $this->so->save($resource);
327
		}
328
		else
329
		{
330
			$resource['res_modifier'] = $GLOBALS['egw_info']['user']['account_id'];
331
			$resource['res_modified'] = Api\DateTime::server2user(time(),'ts');
332
			$old = $this->read($resource['res_id']);
333
		}
334
335
		switch ($resource['picture_src'])
336
		{
337
			case 'own_src':
338
				if($resource['own_file']['size'] > 0)
339
				{
340
					$msg = $this->save_picture($resource['own_file'],$resource['res_id']);
341
					unset($resource['own_file']);
342
					break;
343
				}
344
				elseif(@Vfs::stat('/apps/resources/'.$resource['res_id'].'/'.self::PICTURE_NAME))
345
				{
346
					break;
347
				}
348
				$resource['picture_src'] = 'cat_src';
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
349
			case 'cat_src':
350
				break;
351
			case 'gen_src':
352
				$resource['picture_src'] = 'gen_src';
353
				break;
354
			default:
355
				if($resource['own_file']['size'] > 0)
356
				{
357
					$resource['picture_src'] = 'own_src';
358
					$msg = $this->save_picture($resource['own_file'],$resource['res_id']);
359
				}
360
				else
361
				{
362
					$resource['picture_src'] = 'cat_src';
363
				}
364
		}
365
		// somthing went wrong on saving own picture
366
		if($msg)
367
		{
368
			return $msg;
369
		}
370
371
		// Check for restore of deleted, restore held links
372
		if($old && $old['deleted'] && !$resource['deleted'])
373
		{
374
			Link::restore('resources', $resource['res_id']);
375
		}
376
377
		// delete old pictures
378
		if($resource['picture_src'] != 'own_src')
379
		{
380
			$this->remove_picture($resource['res_id']);
381
		}
382
383
		// Update link title
384
		Link::notify_update('resources',$resource['res_id'], $resource);
385
		// save links
386
		if(is_array($resource['link_to']['to_id']))
387
		{
388
			Link::link('resources',$resource['res_id'],$resource['link_to']['to_id']);
389
		}
390
		if($resource['accessory_of'] != $old['accessory_of'])
391
		{
392
			Link::unlink(0,'resources',$resource['res_id'],'','resources',$old['accessory_of']);
393
394
			// Check for resource changing to accessory - move its accessories to resource
395
			if($old['accessory_of'] == -1 && ($accessories = $this->get_acc_list($resource['res_id'])))
396
			{
397
				foreach($accessories as $accessory => $name)
398
				{
399
					Link::unlink(0,'resources',$accessory,'','resources',$resource['res_id']);
400
					if (($acc = $this->read($accessory)))
401
					{
402
						$acc['accessory_of'] = -1;
403
						$this->so->save($acc);
404
					}
405
				}
406
			}
407
		}
408
		if($resource['accessory_of'] != -1)
409
		{
410
			Link::link('resources',$resource['res_id'],'resources',$resource['accessory_of']);
411
		}
412
413
		if(!empty($resource['res_id']) && $this->so->get_value("cat_id",$resource['res_id']) != $resource['cat_id'] && $resource['accessory_of'] == -1)
414
		{
415
			$accessories = $this->get_acc_list($resource['res_id']);
416
			foreach($accessories as $accessory => $name)
417
			{
418
				if (($acc = $this->so->read($accessory)))
419
				{
420
					$acc['cat_id'] = $resource['cat_id'];
421
					$this->so->save($acc);
422
				}
423
			}
424
		}
425
426
		$res_id = $this->so->save($resource);
427
428
		// History & notifications
429
		if (!is_object($this->tracking))
430
		{
431
			$this->tracking = new resources_tracking();
0 ignored issues
show
Bug Best Practice introduced by
The property tracking does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
432
		}
433
		if ($this->tracking->track($resource,$old,$this->user) === false)
0 ignored issues
show
Bug Best Practice introduced by
The property user does not exist on resources_bo. Did you maybe forget to declare it?
Loading history...
434
		{
435
			return implode(', ',$this->tracking->errors);
436
		}
437
438
		return $res_id ? $res_id : lang('Something went wrong by saving resource');
439
	}
440
441
	/**
442
	 * deletes resource including pictures and links
443
	 *
444
	 * @author Lukas Weiss <[email protected]>
445
	 * @param int $res_id id of resource
446
	 * @return string|false string with error or false on success
447
	 */
448
	function delete($res_id)
449
	{
450
		if(!$this->acl->is_permitted($this->so->get_value('cat_id',$res_id),Acl::DELETE))
451
		{
452
			return lang('You are not permitted to delete this resource!');
453
		}
454
455
		// check if we only mark resources as deleted, or really delete them
456
		$config = Api\Config::read('resources');
457
		if (!($old = $this->read($res_id)))
458
		{
459
			// error is returned at end of function
460
		}
461
		elseif ($config['history'] != '' && $old['deleted'] == null)
462
		{
463
			$old['deleted'] = time();
464
			$this->save($old);
465
			Link::unlink(0,'resources',$res_id,'','','',true);
466
			$accessories = $this->get_acc_list($res_id);
467
			foreach($accessories as $acc_id => $name)
468
			{
469
				// Don't purge already deleted accessories
470
				if (($acc = $this->read($acc_id)) && !$acc['deleted'])
471
				{
472
					$acc['deleted'] = time();
473
					$this->save($acc);
474
					Link::unlink(0,'resources',$acc_id,'','','',true);
475
				}
476
			}
477
			return false;
478
		}
479
		elseif ($this->so->delete(array('res_id'=>$res_id)))
480
		{
481
			$accessories = $this->get_acc_list($res_id, true);
482
			foreach($accessories as $acc_id => $name)
483
			{
484
				if($this->delete($acc_id) && ($acc = $this->read($acc_id)))
485
				{
486
					$acc['accessory_of'] = -1;
487
					$this->save($acc);
488
				}
489
			};
490
			$this->remove_picture($res_id);
491
	 		Link::unlink(0,'resources',$res_id);
492
	 		// delete the resource from the calendar
493
	 		ExecMethod('calendar.calendar_so.deleteaccount','r'.$res_id);
0 ignored issues
show
Deprecated Code introduced by
The function ExecMethod() has been deprecated: use autoloadable class-names, instanciate and call method or use static methods ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

493
	 		/** @scrutinizer ignore-deprecated */ ExecMethod('calendar.calendar_so.deleteaccount','r'.$res_id);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
494
	 		return false;
495
		}
496
		return lang('Something went wrong by deleting resource');
497
	}
498
499
	/**
500
	 * gets list of accessories for resource
501
	 *
502
	 * Cornelius Weiss <[email protected]>
503
	 * @param int $res_id id of resource
504
	 * @param boolean $deleted Include deleted accessories
505
	 * @return array
506
	 */
507
	function get_acc_list($res_id,$deleted=false)
508
	{
509
		if($res_id < 1){return;}
510
		$data = $this->so->search('','res_id,name,deleted','','','','','',$start,array('accessory_of' => $res_id),'',$need_full_no_count=true);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $start seems to be never defined.
Loading history...
511
		$acc_list = array();
512
		if($data) {
513
			foreach($data as $num => $resource)
514
			{
515
				if($resource['deleted'] && !$deleted) continue;
516
				$acc_list[$resource['res_id']] = $resource['name'];
517
			}
518
		}
519
		return $acc_list;
520
	}
521
522
	/**
523
	 * Search for resources for calendar to select as participants
524
	 *
525
	 * Search and options match Link::query()
526
	 *
527
	 * Resources return actual resources as well as categories that match
528
	 *
529
	 * @param String $search - Search string
530
	 * @param Array $options - search options
531
	 * @see Link::query()
532
	 *
533
	 * @return Array List of ID => Title entries matching search string
534
	 */
535
	public static function calendar_search($search, $options)
536
	{
537
		$bo = new resources_bo();
538
539
		// Resources - call direct to avoid cache
540
		$list = $bo->link_query($search, $options);
541
542
		// Categories
543
		$cats = $bo->acl->get_cats(Acl::READ);
544
		foreach($cats as $cat_id => $cat)
545
		{
546
			if($cat && stripos($cat, $search) !== FALSE)
547
			{
548
				// Get resources for that category
549
				if(!$options['exec'])
550
				{
551
					$resources = $bo->get_resources_by_category($cat_id);
552
				}
553
				else
554
				{
555
					$cat_options = $options;
556
					$cat_options['cat_id'] = $cat_id;
557
					$resources = $bo->link_query('',$cat_options);
558
				}
559
560
				// Edit dialog sends exec as an option, don't add categories
561
				if(count($resources) && !$options['exec'])
562
				{
563
					$_resources = array_map(
564
						function($id) { return 'r'.$id;},
565
						array_keys($resources)
566
					);
567
					$list['cat-'.$cat_id] = array(
568
						'label'	=>	$bo->acl->egw_cats->id2name($cat_id),
569
						'resources'	=>	$_resources,
570
					);
571
				}
572
				else if ($resources && $options['exec'])
573
				{
574
					array_map(
575
						function($id,$name) use (&$list) { if(!$list[''.$id]) $list[''.$id] = $name;},
576
						array_keys($resources), $resources
577
					);
578
				}
579
			}
580
		}
581
582
		return $list;
583
	}
584
585
	/**
586
	 * Get a list of resources (ID => name) matching a single category ID
587
	 * @param int $cat_id
588
	 * @return array()
589
	 */
590
	public function get_resources_by_category($cat_id)
591
	{
592
		$resources = array();
593
		$filter = array(
594
			'cat_id' => $cat_id,
595
			//'accessory_of' => '-1'
596
			'deleted' => null
597
		);
598
		$only_keys = 'res_id,name';
599
		$data = $this->so->search(array(),$only_keys,$order_by='name',$extra_cols='',$wildcard='%',$empty,$op='OR',false,$filter);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $empty seems to be never defined.
Loading history...
600
		if(is_array($data) && $data)
601
		{
602
			foreach($data as $resource)
603
			{
604
				$resources[$resource['res_id']] = $resource['name'];
605
			}
606
		}
607
608
		return $resources;
609
	}
610
611
	/**
612
	 * returns info about resource for calender
613
	 * @author Cornelius Weiss<[email protected]>
614
	 * @param int|array|string $res_id single id, array $num => $res_id or
615
	 *	'cat-<cat_id>' for the whole category
616
	 * @return array
617
	 */
618
	function get_calendar_info($res_id)
619
	{
620
		//error_log(__METHOD__ . "(".print_r($res_id,true).")");
621
622
		// Resource category
623
		if(is_string($res_id) && strpos($res_id, 'cat-') === 0)
624
		{
625
			$cat_id = (int)substr($res_id, 4);
626
			if(!$this->acl->is_permitted($cat_id, Acl::READ))
627
			{
628
				return array();
629
			}
630
			return array( array(
631
				'name' => $this->acl->get_cat_name($cat_id),
632
				'rights' => $this->acl->get_permissions($cat_id),
633
				'resources' => array_map(
634
					function($id) { return 'r'.$id;},
635
					array_keys($this->get_resources_by_category($cat_id))
636
				)
637
			));
638
		}
639
640
		if(!is_array($res_id) && $res_id < 1) return;
641
642
		$data = $this->so->search(array('res_id' => $res_id),self::TITLE_COLS.',useable');
643
		if (!is_array($data))
644
		{
645
			//error_log(__METHOD__." No Calendar Data found for Resource with id $res_id");
646
			return array();
647
		}
648
		foreach($data as $num => &$resource)
649
		{
650
			$resource['rights'] = false;
651
			foreach($this->cal_right_transform as $res_right => $cal_right)
652
			{
653
				if($this->acl->is_permitted($resource['cat_id'],$res_right))
654
				{
655
					$resource['rights'] = $cal_right;
656
				}
657
			}
658
			$resource['responsible'] = $this->acl->get_cat_admin($resource['cat_id']);
659
660
			// preseed the cache
661
			Link::set_cache('resources',$resource['res_id'],$t=$this->link_title($resource));
662
		}
663
		return $data;
664
	}
665
666
	/**
667
	 * returns status for a new calendar entry depending on resources ACL
668
	 * @author Cornelius Weiss <[email protected]>
669
	 * @param int $res_id single id
670
	 * @return string|boolean false if resource not found, no read rights or not bookable, else A if user has direkt booking rights or U if no dirket booking
671
	 */
672
	function get_calendar_new_status($res_id)
673
	{
674
		if (!($data = $this->read($res_id)) || !$data['bookable'])
675
		{
676
			return false;
677
		}
678
		return $this->acl->is_permitted($data['cat_id'],resources_acl_bo::DIRECT_BOOKING) ? A : U;
0 ignored issues
show
Bug introduced by
The constant A was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant U was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
679
	}
680
681
	/**
682
	 * @author Cornelius Weiss <[email protected]>
683
	 * query infolog for entries matching $pattern
684
	 * @param string|array $pattern if it's a string it is the string we will search for as a criteria, if it's an array we
685
	 * 	will seach for 'search' key in this array to get the string criteria. others keys handled are actually used
686
	 *	for calendar disponibility.
687
	 * @param array $options Array of options for the search
688
	 *
689
	 */
690
	function link_query( $pattern, Array &$options = array() )
691
	{
692
		if (is_array($pattern))
693
		{
694
			$criteria =array('name' => $pattern['search']
695
					,'short_description' => $pattern['search']);
696
		}
697
		else
698
		{
699
			$criteria = array('name' => $pattern
700
				, 'short_description' => $pattern);
701
		}
702
		$only_keys = 'res_id,name,short_description,bookable,useable,quantity';
703
704
		// If no read access to any category, just stop
705
		if(!$this->acl->get_cats(Acl::READ))
706
		{
707
			$options['total'] = 0;
708
			return array();
709
		}
710
		$filter = array(
711
			'cat_id' => array_flip((array)$this->acl->get_cats(Acl::READ)),
712
			//'accessory_of' => '-1'
713
			'deleted' => null
714
		);
715
		$limit = false;
716
		if($options['start'] || $options['num_rows']) {
717
			$limit = array($options['start'], $options['num_rows']);
718
		}
719
		if($options['cat_id'] && in_array($options['cat_id'], $filter['cat_id']))
720
		{
721
			$filter['cat_id'] = $options['cat_id'];
722
		}
723
		if($options['accessory_of'])
724
		{
725
			$filter['accessory_of'] = $options['accessory_of'];
726
		}
727
		$list = array();
728
		$data = $this->so->search($criteria,$only_keys,$order_by='name',$extra_cols='',$wildcard='%',$empty,$op='OR',$limit,$filter);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $empty seems to be never defined.
Loading history...
729
		// we need to check availability of the searched resources in the calendar if $pattern ['exec'] contains some extra args
730
		$show_conflict=False;
0 ignored issues
show
Unused Code introduced by
The assignment to $show_conflict is dead and can be removed.
Loading history...
731
		if ($data && $options['exec'])
732
		{
733
			// we'll use a cache for resources info taken from database
734
			static $res_info_cache = array();
735
			$cal_info=$options['exec'];
736
			if ( isset($cal_info['start']) && isset($cal_info['duration']))
737
			{
738
				//get a calendar objet for reservations
739
				if ( (!isset($this->bocal)) || !(is_object($this->bocal)))
740
				{
741
					$this->bocal = new calendar_bo();
0 ignored issues
show
Bug Best Practice introduced by
The property bocal does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
742
				}
743
				$start = new Api\DateTime($cal_info['start']);
744
				$startarr= getdate($start->format('ts'));
745
				if (isset($cal_info['whole_day']) && $cal_info['whole_day']) {
746
					$startarr['hour'] = $startarr['minute'] = 0;
747
					$start = new Api\DateTime($startarr);
748
					$end = $start->format('ts') + 86399;
749
				} else {
750
					$start = $start->format('ts');
751
					$end = $start + ($cal_info['duration']);
752
				}
753
754
				// search events matching our timestamps
755
 				$resource_list=array();
756
				foreach($data as $num => $resource)
757
				{
758
					// we only need resources id for the search, but with a 'r' prefix
759
					// now we take this loop to store a new resource array indexed with resource id
760
					// and as we work for calendar we use only bookable resources
761
					if ((isset($resource['bookable'])) && ($resource['bookable'])){
762
						$res_info_cache[$resource['res_id']]=$resource;
763
						$resource_list[]='r'.$resource['res_id'];
764
					}
765
				}
766
				$overlapping_events =& $this->bocal->search(array(
767
					'start' => $start,
768
					'end'   => $end,
769
					'users' => $resource_list,
770
					'ignore_acl' => true,   // otherwise we get only events readable by the user
771
					'enum_groups' => false,  // otherwise group-events would not block time
772
				));
773
774
				// parse theses overlapping events
775
				foreach($overlapping_events as $event)
776
				{
777
					if ($event['non_blocking']) continue; // ignore non_blocking events
778
					if (isset($cal_info['event_id']) && $event['id']==$cal_info['event_id']) {
779
						continue; //ignore this event, it's the current edited event, no conflict by def
780
					}
781
					// now we are interested only on resources booked by theses events
782
					if (isset($event['participants']) && is_array($event['participants'])){
783
						foreach($event['participants'] as $part_key => $part_detail){
784
							if ($part_key{0}=='r')
785
							{ //now we gatta resource here
786
								//need to check the quantity of this resource
787
								$resource_id=substr($part_key,1);
788
								// if we do not find this resource in our indexed array it's certainly
789
								// because it was unset, non bookable maybe
790
								if (!isset($res_info_cache[$resource_id])) continue;
791
								// to detect ressources with default to 1 quantity
792
								if (!isset($res_info_cache[$resource_id]['useable'])) {
793
									$res_info_cache[$resource_id]['useable'] = 1;
794
								}
795
								// now decrement this quantity useable
796
								$quantity = 1;
797
								$this->bocal->so->split_status($part_detail,$quantity);
798
799
								$res_info_cache[$resource_id]['useable']-=$quantity;
800
							}
801
						}
802
					}
803
				}
804
			}
805
		}
806
		if (isset($res_info_cache)) {
807
			$show_conflict= $GLOBALS['egw_info']['user']['preferences']['calendar']['defaultresource_sel'] !== 'resources_without_conflict';
808
			// if we have this array indexed on resource id it means non-bookable resource are removed and we are working for calendar
809
			// so we'll loop on this one and not $data
810
			foreach($res_info_cache as $id => $resource) {
811
				//maybe this resource is reserved
812
				if ( ($resource['useable'] < 1) )
813
				{
814
					if($show_conflict)
815
					{
816
						$list[$id] = ' ('.lang('conflict').') '.$resource['name']. ($resource['short_description'] ? ', ['.$resource['short_description'].']':'');
817
					}
818
				}
819
				else
820
				{
821
					$list[$id] = $resource['name']. ($resource['short_description'] ? ', ['.$resource['short_description'].']':'') .
822
							($resource['useable'] > 1 ? " ( {$resource['useable']} / {$resource['quantity']} )" : '');
823
				}
824
			}
825
		} else {
826
			// we are not working for the calendar, we loop on the initial $data
827
			if (is_array($data)) {
828
				foreach($data as $num => $resource)
829
				{
830
					$id=$resource['res_id'];
831
					$list[$id] = $resource['name']. ($resource['short_description'] ? ', ['.$resource['short_description'].']':'');
832
				}
833
			} else {
834
				error_log(__METHOD__." No Data found for Resource with id ".$resource['res_id']);
835
			}
836
		}
837
		$options['total'] = $this->so->total;
838
		return $list;
839
	}
840
841
	/**
842
	 * get title for an infolog entry identified by $res_id
843
	 *
844
	 * @author Cornelius Weiss <[email protected]>
845
	 * @param int|array $resource
846
	 * @return string|boolean string with title, null if resource does not exist or false if no perms to view it
847
	 */
848
	function link_title( $resource )
849
	{
850
		if (!is_array($resource))
851
		{
852
			if (!($resource  = $this->read(array('res_id' => $resource)))) return $resource;
853
		}
854
		elseif (!$this->acl->is_permitted($resource['cat_id'],Acl::READ))
855
		{
856
			return false;
857
		}
858
		return $resource['name']. ($resource['short_description'] ? ', ['.$resource['short_description'].']':'');
859
	}
860
861
	/**
862
	 * Columns displayed in title (or required for ACL)
863
	 *
864
	 */
865
	const TITLE_COLS = 'res_id,name,short_description,cat_id';
866
867
	/**
868
	 * get title for multiple contacts identified by $ids
869
	 *
870
	 * Is called as hook to participate in the linking.
871
	 *
872
	 * @param array $ids array with resource-id's
873
	 * @return array with titles, see link_title
874
	 */
875
	function link_titles(array $ids)
876
	{
877
		$titles = array();
878
		if (($resources =& $this->so->search(array('res_id' => $ids),self::TITLE_COLS)))
879
		{
880
			foreach($resources as $resource)
881
			{
882
				$titles[$resource['res_id']] = $this->link_title($resource);
883
			}
884
		}
885
		// we assume all not returned contacts are not readable for the user (as we report all deleted contacts to Link)
886
		foreach($ids as $id)
887
		{
888
			if (!isset($titles[$id]))
889
			{
890
				$titles[$id] = false;
891
			}
892
		}
893
		return $titles;
894
	}
895
896
	/**
897
	 * saves a pictures in vfs
898
	 *
899
	 * Cornelius Weiss <[email protected]>
900
	 * @param array $file array with key => value
901
	 * @param int $resource_id
902
	 * @return mixed string with msg if somthing went wrong; nothing if all right
903
	 */
904
	function save_picture($file,$resource_id)
905
	{
906
		if ($file['type'] == 'application/octet-stream')
907
		{
908
			$file['type'] = Api\MimeMagic::filename2mime($file['name']);
909
		}
910
		switch($file['type'])
911
		{
912
			case 'image/gif':
913
				$src_img = imagecreatefromgif($file['tmp_name']);
914
				break;
915
			case 'image/jpeg':
916
			case 'image/pjpeg':
917
				$src_img = imagecreatefromjpeg($file['tmp_name']);
918
				break;
919
			case 'image/png':
920
			case 'image/x-png':
921
				$src_img = imagecreatefrompng($file['tmp_name']);
922
				break;
923
			default:
924
				return $file['type'].': '.lang('Picture type is not supported, sorry!');
925
		}
926
927
		$tmp_name = tempnam($GLOBALS['egw_info']['server']['temp_dir'],'resources-picture');
928
		imagejpeg($src_img,$tmp_name);
929
		imagedestroy($src_img);
930
931
		Link::attach_file('resources',$resource_id,array(
932
			'tmp_name' => $tmp_name,
933
			'name'     => self::PICTURE_NAME,
934
			'type'     => 'image/jpeg',
935
		));
936
	}
937
938
	/**
939
	 * get resource picture either from vfs or from symlink
940
	 * Cornelius Weiss <[email protected]>
941
	 * @param int|array $resource res-id or whole resource array
942
	 * @param bool $fullsize false = thumb, true = full pic
943
	 * @return string url of picture
944
	 */
945
	function get_picture($resource,$fullsize=false)
946
	{
947
		if ($resource && !is_array($resource)) $resource = $this->read($resource);
948
949
		switch($resource['picture_src'])
950
		{
951
			case 'own_src':
952
				$picture = Link::vfs_path('resources',$resource['res_id'],self::PICTURE_NAME,true);	// vfs path
953
				if ($fullsize)
954
				{
955
					$picture = Egw::link(Vfs::download_url($picture));
956
				}
957
				else
958
				{
959
					$picture = Egw::link('/api/thumbnail.php', array(
960
						'path' => $picture
961
					),false);
962
				}
963
				break;
964
965
			case 'cat_src':
966
				$picture = Api\Categories::id2name($resource['cat_id'], 'data');
967
				if($picture['icon'])
968
				{
969
					$picture = !$fullsize?$GLOBALS['egw_info']['server']['webserver_url'].self::ICON_PATH.'/'.$picture['icon']:self::ICON_PATH.'/'.$picture['icon'];
970
					break;
971
				}
972
				// fall through
973
			case 'gen_src':
974
			default :
975
				$src = $resource['picture_src'];
976
				$picture = !$fullsize?$GLOBALS['egw_info']['server']['webserver_url'].$this->resource_icons:$this->resource_icons;
977
				$picture .= strpos($src,'.') !== false ? $src : 'generic.png';
978
		}
979
		return $picture;
980
	}
981
982
	/**
983
	 * removes picture from vfs
984
	 *
985
	 * Cornelius Weiss <[email protected]>
986
	 * @param int $res_id id of resource
987
	 * @return bool succsess or not
988
	 */
989
	function remove_picture($res_id)
990
	{
991
		if (($arr = Link::delete_attached('resources',$res_id,self::PICTURE_NAME)) && is_array($arr))
0 ignored issues
show
introduced by
The condition is_array($arr) is always false.
Loading history...
992
		{
993
			return array_shift($arr);	// $arr = array($path => (bool)$ok);
994
		}
995
		return false;
996
	}
997
998
	/**
999
	 * get_genpicturelist
1000
	 * gets all pictures from 'generic picutres dir' in selectbox style for eTemplate
1001
	 *
1002
	 * Cornelius Weiss <[email protected]>
1003
	 * @return array directory contens in eTemplates selectbox style
1004
	 */
1005
	function get_genpicturelist()
1006
	{
1007
		$icons['generic.png'] = lang('gernal resource');
0 ignored issues
show
Comprehensibility Best Practice introduced by
$icons was never initialized. Although not strictly required by PHP, it is generally a good practice to add $icons = array(); before regardless.
Loading history...
1008
		$dir = dir(EGW_SERVER_ROOT.$this->resource_icons);
1009
		while($file = $dir->read())
1010
		{
1011
			if (preg_match('/\\.(png|gif|jpe?g)$/i',$file) && $file != 'generic.png')
1012
			{
1013
				$icons[$file] = substr($file,0,strpos($file,'.'));
1014
			}
1015
		}
1016
		$dir->close();
1017
		return $icons;
1018
	}
1019
}
1020