Issues (4868)

timesheet/inc/class.timesheet_import_csv.inc.php (20 issues)

1
<?php
2
/**
3
 * eGroupWare
4
 *
5
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
6
 * @package timesheet
7
 * @subpackage importexport
8
 * @link http://www.egroupware.org
9
 * @author Nathan Gray
10
 * @copyright 2011 Nathan Gray
11
 * @version $Id$
12
 */
13
14
use EGroupware\Api;
15
use EGroupware\Api\Link;
16
17
/**
18
 * class import_csv for timesheet
19
 */
20
class timesheet_import_csv extends importexport_basic_import_csv
21
{
22
	public static $special_fields = array(
23
		'addressbook'     => 'Link to Addressbook, use nlast,nfirst[,org] or contact_id from addressbook',
24
		'link_1'      => '1. link: appname:appid the entry should be linked to, eg.: addressbook:123',
25
		'link_2'      => '2. link: appname:appid the entry should be linked to, eg.: addressbook:123',
26
		'link_3'      => '3. link: appname:appid the entry should be linked to, eg.: addressbook:123',
27
	);
28
29
	/**
30
	 * conditions for actions
31
	 *
32
	 * @var array
33
	 */
34
	protected static $conditions = array( 'exists' );
35
36
	/**
37
	 * @var business object
0 ignored issues
show
The type business was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
38
	 */
39
	private $bo;
40
41
	/**
42
	* For figuring out if a record has changed
43
	*/
44
	protected $tracking;
45
46
	/**
47
	 * Initialize for import
48
	 *
49
	 * @param definition $_definition
0 ignored issues
show
The type definition was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
50
	 */
51
	protected function init( importexport_definition $_definition, importexport_import_csv &$import_csv )
52
	{
53
		// fetch the bo
54
		$this->bo = new timesheet_bo();
0 ignored issues
show
Documentation Bug introduced by
It seems like new timesheet_bo() of type timesheet_bo is incompatible with the declared type business of property $bo.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
55
56
		// Get the tracker for changes
57
		$this->tracking = new timesheet_tracking($this->bo);
58
59
		// Add extra conversions
60
		$import_csv->conversion_class = $this;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this of type timesheet_import_csv is incompatible with the declared type class of property $conversion_class.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
61
62
		// set Owner
63
		$plugin_options = $_definition->plugin_options;
0 ignored issues
show
Bug Best Practice introduced by
The property plugin_options does not exist on importexport_definition. Since you implemented __get, consider adding a @property annotation.
Loading history...
64
		$plugin_options['record_owner'] = isset( $_definition->plugin_options['record_owner'] ) ?
65
			$_definition->plugin_options['record_owner'] : $this->user;
66
		$_definition->plugin_options = $plugin_options;
0 ignored issues
show
Bug Best Practice introduced by
The property plugin_options does not exist on importexport_definition. Since you implemented __set, consider adding a @property annotation.
Loading history...
67
68
		// For converting human-friendly lookups
69
		$this->lookups = array(
70
			'ts_status'	=> $this->bo->status_labels
71
		);
72
73
		// Status need leading spaces removed
74
		foreach($this->lookups['ts_status'] as $id => &$label)
75
		{
76
			$label = str_replace('&nbsp;', '',trim($label));
77
			if($label == '') unset($this->lookups['ts_status'][$id]);
78
		}
79
	}
80
81
	/**
82
	 *Import a single record
83
	 *
84
 	 * You don't need to worry about mappings or translations, they've been done already.
85
	 * You do need to handle the conditions and the actions taken.
86
	 *
87
	 * Updates the count of actions taken
88
	 *
89
	 * @return boolean success
90
	 */
91
	protected function import_record(importexport_iface_egw_record &$record, &$import_csv)
92
	{
93
		// Reset BO data for new record
94
		$this->bo->data = array();
95
		
96
		// Automatically handle text Api\Categories without explicit Api\Translation
97
		foreach(array('ts_status','cat_id') as $field)
98
		{
99
			if(!is_numeric($record->$field))
100
			{
101
				$translate_key = 'translate'.(substr($field,0,2) == 'ts' ? substr($field,2) : '_cat_id');
102
				if(!is_null($this->lookups[$field]) && ($key = array_search($record->$field, $this->lookups[$field])))
103
				{
104
					$record->$field = $key;
105
				}
106
				elseif(array_key_exists($translate_key, $_definition->plugin_options))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $_definition seems to be never defined.
Loading history...
107
				{
108
					$t_field = $_definition->plugin_options[$translate_key];
109
					switch ($t_field)
110
					{
111
						case '':
112
						case '0':
113
							// Skip that field
114
							unset($record->$field);
115
							break;
116
						case '~skip~':
117
							continue 2;
118
						default:
119
							if(strpos($t_field, 'add') === 0)
120
							{
121
								// Check for a parent
122
								list($name, $parent_name) = explode('~',$t_field);
123
								if($parent_name)
124
								{
125
									$parent = importexport_helper_functions::cat_name2id($parent_name);
126
								}
127
128
								if($field == 'cat_id')
129
								{
130
									$record->$field = importexport_helper_functions::cat_name2id($record->$field, $parent);
131
								}
132
								elseif ($field == 'ts_status')
133
								{
134
									end($this->bo->status_labels);
135
									$id = key($this->bo->status_labels)+1;
136
									$this->bo->status_labels[$id] = $record->$field;
137
									$this->bo->status_labels_config[$id] = array(
138
										'name'   => $record->$field,
139
										'parent' => $parent,
140
										'admin'  => false
141
									);
142
									Api\Config::save_value('status_labels',$this->bo->status_labels_config,TIMESHEET_APP);
143
									$lookups[$field][$id] = $name;
144
									$record->$field = $id;
145
								}
146
							}
147
							elseif(!is_null($lookups[$field]) && ($key = array_search($t_field, $lookups[$field])))
148
							{
149
								$record->$field = $key;
150
							}
151
							else
152
							{
153
								$record->$field = $t_field;
154
							}
155
							break;
156
					}
157
				}
158
			}
159
		}
160
161
			// Set creator, unless it's supposed to come from CSV file
162
		if($_definition->plugin_options['owner_from_csv'] && $record->ts_owner)
0 ignored issues
show
Bug Best Practice introduced by
The property ts_owner does not exist on importexport_iface_egw_record. Since you implemented __get, consider adding a @property annotation.
Loading history...
163
		{
164
			if(!is_numeric($record->ts_owner))
165
			{
166
				// Automatically handle text owner without explicit Api\Translation
167
				$new_owner = importexport_helper_functions::account_name2id($record->ts_owner);
168
				if($new_owner == '')
169
				{
170
					$this->errors[$import_csv->get_current_position()] = lang(
171
						'Unable to convert "%1" to account ID.  Using plugin setting (%2) for %3.',
172
						$record->ts_owner,
0 ignored issues
show
The call to lang() has too many arguments starting with $record->ts_owner. ( Ignorable by Annotation )

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

172
					$this->errors[$import_csv->get_current_position()] = /** @scrutinizer ignore-call */ lang(

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. Please note the @ignore annotation hint above.

Loading history...
173
						Api\Accounts::username($_definition->plugin_options['record_owner']),
174
						lang($this->bo->field2label['ts_owner'])
175
					);
176
					$record->ts_owner = $_definition->plugin_options['record_owner'];
0 ignored issues
show
Bug Best Practice introduced by
The property ts_owner does not exist on importexport_iface_egw_record. Since you implemented __set, consider adding a @property annotation.
Loading history...
177
				}
178
				else
179
				{
180
					$record->ts_owner = $new_owner;
181
				}
182
			}
183
		}
184
		elseif ($_definition->plugin_options['record_owner'])
185
		{
186
			$record->ts_owner = $_definition->plugin_options['record_owner'];
187
		}
188
189
		// Check account IDs
190
		foreach(array('ts_modifier') as $field)
191
		{
192
			if($record->$field && !is_numeric($record->$field))
193
			{
194
				// Try an automatic conversion
195
				$account_id = importexport_helper_functions::account_name2id($record->$field);
196
				if($account_id && strtoupper(Api\Accounts::username($account_id)) == strtoupper($record->$field))
197
				{
198
					$record->$field = $account_id;
199
				}
200
				else
201
				{
202
					$this->errors[$import_csv->get_current_position()] = lang(
203
						'Unable to convert "%1" to account ID.  Using plugin setting (%2) for %3.',
204
						$record->$field,
205
						Api\Accounts::username($_definition->plugin_options['record_owner']),
206
						$this->bo->field2label[$field] ? lang($this->bo->field2label[$field]) : $field
207
					);
208
				}
209
			}
210
		}
211
212
		// Special values
213
		if ($record->addressbook && !is_numeric($record->addressbook))
0 ignored issues
show
Bug Best Practice introduced by
The property addressbook does not exist on importexport_iface_egw_record. Since you implemented __get, consider adding a @property annotation.
Loading history...
214
		{
215
			list($lastname,$firstname,$org_name) = explode(',',$record->addressbook);
216
			$record->addressbook = self::addr_id($lastname,$firstname,$org_name);
0 ignored issues
show
Bug Best Practice introduced by
The property addressbook does not exist on importexport_iface_egw_record. Since you implemented __set, consider adding a @property annotation.
Loading history...
217
		}
218
		if ($record->pm_id && !is_numeric($record->pm_id))
0 ignored issues
show
Bug Best Practice introduced by
The property pm_id does not exist on importexport_iface_egw_record. Since you implemented __get, consider adding a @property annotation.
Loading history...
219
		{
220
			$pms = Link::query('projectmanager',$record->pm_id);
221
			$record->pm_id = key($pms);
0 ignored issues
show
Bug Best Practice introduced by
The property pm_id does not exist on importexport_iface_egw_record. Since you implemented __set, consider adding a @property annotation.
Loading history...
222
		}
223
224
		if ( $_definition->plugin_options['conditions'] )
225
		{
226
			foreach ( $_definition->plugin_options['conditions'] as $condition )
227
			{
228
				$results = array();
229
				switch ( $condition['type'] )
230
				{
231
					// exists
232
					case 'exists' :
233
						if($record->$condition['string'])
234
						{
235
							$results = $this->bo->search(array($condition['string'] => $record->$condition['string']));
236
						}
237
238
						if ( is_array( $results ) && count( array_keys( $results )) >= 1 )
239
						{
240
							// apply action to all records matching this exists condition
241
							$action = $condition['true'];
242
							foreach ( (array)$results as $result )
243
							{
244
								$record->ts_id = $result['ts_id'];
245
								if ( $_definition->plugin_options['update_cats'] == 'add' )
246
								{
247
									if ( !is_array( $result['cat_id'] ) ) $result['cat_id'] = explode( ',', $result['cat_id'] );
248
									if ( !is_array( $record->cat_id ) ) $record->cat_id = explode( ',', $record->cat_id );
249
									$record->cat_id = implode( ',', array_unique( array_merge( $record->cat_id, $result['cat_id'] ) ) );
250
								}
251
								$success = $this->action(  $action['action'], $record, $import_csv->get_current_position() );
252
							}
253
						}
254
						else
255
						{
256
							$action = $condition['false'];
257
							$success = ($this->action(  $action['action'], $record, $import_csv->get_current_position() ));
258
						}
259
						break;
260
261
					// not supported action
262
					default :
263
						die('condition / action not supported!!!');
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
264
						break;
265
				}
266
				if ($action['last']) break;
267
			}
268
		}
269
		else
270
		{
271
			// unconditional insert
272
			$success = $this->action( 'insert', $record, $import_csv->get_current_position() );
273
		}
274
275
		return $success;
276
	}
277
278
	/**
279
	 * perform the required action
280
	 *
281
	 * @param int $_action one of $this->actions
282
	 * @param array $_data tracker data for the action
283
	 * @return bool success or not
284
	 */
285
	protected function action ( $_action, importexport_iface_egw_record &$record, $record_num = 0 )
286
	{
287
		$_data = $record->get_record_array();
288
		$result = true;
289
		switch ($_action)
290
		{
291
			case 'none' :
292
				return true;
293
			case 'update' :
294
				// Only update if there are changes
295
				$old_record = new timesheet_egw_record($_data['ts_id']);
296
				$old = $old_record->get_record_array();
297
298
				if(!$this->definition->plugin_options['change_owner'])
299
				{
300
					// Don't change creator of an existing ticket
301
					unset($_data['ts_owner']);
302
				}
303
304
				// Merge to deal with fields not in import record
305
				$_data = array_merge($old, $_data);
306
				$changed = $this->tracking->changed_fields($_data, $old);
307
				if(count($changed) == 0 && !$this->definition->plugin_options['update_timestamp'])
308
				{
309
					break;
310
				}
311
312
				// Clear old link, if different
313
				if ($_data['ts_id'] && array_key_exists('pm_id', $_data) && $_data['pm_id'] != $old['pm_id'])
314
				{
315
					Link::unlink2(0,TIMESHEET_APP,$_data['ts_id'],0,'projectmanager',$old['pm_id']);
316
				}
317
318
				// Fall through
319
			case 'insert' :
320
				if ( $this->dry_run )
321
				{
322
					//print_r($_data);
323
					$this->results[$_action]++;
324
					break;
325
				}
326
				else
327
				{
328
					$result = $this->bo->save( $_data);
329
					$_data['ts_id'] = $this->bo->data['ts_id'];
330
331
					// Set projectmanager link
332
					if ($_data['pm_id'])
333
					{
334
						Link::link(TIMESHEET_APP,$_data['ts_id'],'projectmanager',$_data['pm_id']);
335
336
						// notify the link-class about the update, as other apps may be subscribed to it
337
						Link::notify_update(TIMESHEET_APP,$this->data['ts_id'],$this->data);
338
					}
339
340
					if($result)
341
					{
342
						$this->errors[$record_num] = lang('Permissions error - %1 could not %2',
343
							$GLOBALS['egw']->accounts->id2name($_data['owner']),
0 ignored issues
show
The call to lang() has too many arguments starting with $GLOBALS['egw']->account...d2name($_data['owner']). ( Ignorable by Annotation )

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

343
						$this->errors[$record_num] = /** @scrutinizer ignore-call */ lang('Permissions error - %1 could not %2',

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. Please note the @ignore annotation hint above.

Loading history...
344
							lang($_action)
345
						) . ' ' . $result;
346
					}
347
					else
348
					{
349
						$this->results[$_action]++;
350
						$result = $this->bo->data['ts_id'];
351
					}
352
					break;
353
				}
354
			default:
355
				throw new Api\Exception('Unsupported action');
356
		}
357
358
		// Process some additional fields
359
		if(!is_numeric($result))
360
		{
361
			return $result;
362
		}
363
		$_link_id = false;
0 ignored issues
show
The assignment to $_link_id is dead and can be removed.
Loading history...
364
		foreach(self::$special_fields as $field => $desc)
365
		{
366
			if(!$_data[$field]) continue;
367
368
			// Links
369
			if(strpos($field, 'link') === 0)
370
			{
371
				list($app, $app_id) = explode(':', $_data[$field]);
372
			}
373
			else
374
			{
375
				$app = $field;
376
				$app_id = $_data[$field];
377
			}
378
			if ($app && $app_id)
379
			{
380
				$link_id = Link::link('timesheet',$_data['ts_id'],$app,$app_id);
0 ignored issues
show
The assignment to $link_id is dead and can be removed.
Loading history...
381
			}
382
		}
383
		return true;
384
	}
385
386
	/**
387
	 * returns translated name of plugin
388
	 *
389
	 * @return string name
390
	 */
391
	public static function get_name()
392
	{
393
		return lang('Timesheet CSV import');
394
	}
395
396
	/**
397
	 * returns translated (user) description of plugin
398
	 *
399
	 * @return string descriprion
400
	 */
401
	public static function get_description()
402
	{
403
		return lang("Imports entries into the timesheet from a CSV File. ");
404
	}
405
406
	/**
407
	 * retruns file suffix(s) plugin can handle (e.g. csv)
408
	 *
409
	 * @return string suffix (comma seperated)
410
	 */
411
	public static function get_filesuffix()
412
	{
413
		return 'csv';
414
	}
415
416
	/**
417
	 * return etemplate components for options.
418
	 * @abstract We can't deal with etemplate objects here, as an uietemplate
419
	 * objects itself are scipt orientated and not "dialog objects"
420
	 *
421
	 * @return array (
422
	 * 		name 		=> string,
423
	 * 		content		=> array,
424
	 * 		sel_options => array,
425
	 * 		preserv		=> array,
426
	 * )
427
	 */
428
	public function get_options_etpl(importexport_definition &$definition=null)
429
	{
430
		// lets do it!
431
	}
432
433
	/**
434
	 * returns etemplate name for slectors of this plugin
435
	 *
436
	 * @return string etemplate name
437
	 */
438
	public function get_selectors_etpl()
439
	{
440
		// lets do it!
441
	}
442
443
	/**
444
	* Returns warnings that were encountered during importing
445
	* Maximum of one warning message per record, but you can append if you need to
446
	*
447
	* @return Array (
448
	*       record_# => warning message
449
	*       )
450
	*/
451
	public function get_warnings()
452
	{
453
		return $this->warnings;
454
	}
455
456
	/**
457
	* Returns errors that were encountered during importing
458
	* Maximum of one error message per record, but you can append if you need to
459
	*
460
	* @return Array (
461
	*       record_# => error message
462
	*       )
463
	*/
464
	public function get_errors()
465
	{
466
		return $this->errors;
467
	}
468
469
	/**
470
	* Returns a list of actions taken, and the number of records for that action.
471
	* Actions are things like 'insert', 'update', 'delete', and may be different for each plugin.
472
	*
473
	* @return Array (
474
	*       action => record count
475
	* )
476
	*/
477
	public function get_results()
478
	{
479
			return $this->results;
480
	}
481
	// end of iface_export_plugin
482
483
	// Extra conversion functions - must be static
484
	public static function addr_id( $n_family,$n_given=null,$org_name=null )
485
	{
486
487
		// find in Addressbook, at least n_family AND (n_given OR org_name) have to match
488
		static $contacts;
489
		if (is_null($n_given) && is_null($org_name))
490
		{
491
			// Maybe all in one
492
			list($n_family, $n_given, $org_name) = explode(',', $n_family);
493
		}
494
		$n_family = trim($n_family);
495
		if(!is_null($n_given)) $n_given = trim($n_given);
496
		if (!is_object($contacts))
497
		{
498
			$contacts =& CreateObject('phpgwapi.contacts');
0 ignored issues
show
Deprecated Code introduced by
The function CreateObject() has been deprecated: use autoloadable class-names and new ( Ignorable by Annotation )

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

498
			$contacts =& /** @scrutinizer ignore-deprecated */ CreateObject('phpgwapi.contacts');

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...
499
		}
500
		if (!is_null($org_name))        // org_name given?
501
		{
502
			$org_name = trim($org_name);
503
			$addrs = $contacts->read( 0,0,array('id'),'',"n_family=$n_family,n_given=$n_given,org_name=$org_name" );
504
			if (!count($addrs))
505
			{
506
				$addrs = $contacts->read( 0,0,array('id'),'',"n_family=$n_family,org_name=$org_name",'','n_family,org_name');
507
			}
508
		}
509
		if (!is_null($n_given) && (is_null($org_name) || !count($addrs)))       // first name given and no result so far
510
		{
511
			$addrs = $contacts->search(array('n_family' => $n_family, 'n_given' => $n_given));
512
		}
513
		if (is_null($n_given) && is_null($org_name))    // just one name given, check against fn (= full name)
514
		{
515
			$addrs = $contacts->read( 0,0,array('id'),'',"n_fn=$n_family",'','n_fn' );
516
		}
517
		if (count($addrs))
518
		{
519
			return $addrs[0]['id'];
520
		}
521
		return False;
522
	}
523
}
524
?>
0 ignored issues
show
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...
525