Issues (4069)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

include/parsecsv.lib.php (10 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
class parseCSV {
4
5
/*
6
7
	Class: parseCSV v0.4.3 beta
8
	http://code.google.com/p/parsecsv-for-php/
9
10
11
	Fully conforms to the specifications lined out on wikipedia:
12
	 - http://en.wikipedia.org/wiki/Comma-separated_values
13
14
	Based on the concept of Ming Hong Ng's CsvFileParser class:
15
	 - http://minghong.blogspot.com/2006/07/csv-parser-for-php.html
16
17
18
19
	Copyright (c) 2007 Jim Myhrberg ([email protected]).
20
21
	Permission is hereby granted, free of charge, to any person obtaining a copy
22
	of this software and associated documentation files (the "Software"), to deal
23
	in the Software without restriction, including without limitation the rights
24
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25
	copies of the Software, and to permit persons to whom the Software is
26
	furnished to do so, subject to the following conditions:
27
28
	The above copyright notice and this permission notice shall be included in
29
	all copies or substantial portions of the Software.
30
31
	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
37
	THE SOFTWARE.
38
39
40
41
	Code Examples
42
	----------------
43
	# general usage
44
	$csv = new parseCSV('data.csv');
45
	print_r($csv->data);
46
	----------------
47
	# tab delimited, and encoding conversion
48
	$csv = new parseCSV();
49
	$csv->encoding('UTF-16', 'UTF-8');
50
	$csv->delimiter = "\t";
51
	$csv->parse('data.tsv');
52
	print_r($csv->data);
53
	----------------
54
	# auto-detect delimiter character
55
	$csv = new parseCSV();
56
	$csv->auto('data.csv');
57
	print_r($csv->data);
58
	----------------
59
	# modify data in a csv file
60
	$csv = new parseCSV();
61
	$csv->sort_by = 'id';
62
	$csv->parse('data.csv');
63
	# "4" is the value of the "id" column of the CSV row
64
	$csv->data[4] = array('firstname' => 'John', 'lastname' => 'Doe', 'email' => '[email protected]');
65
	$csv->save();
66
	----------------
67
	# add row/entry to end of CSV file
68
	#  - only recommended when you know the extact sctructure of the file
69
	$csv = new parseCSV();
70
	$csv->save('data.csv', array('1986', 'Home', 'Nowhere', ''), true);
71
	----------------
72
	# convert 2D array to csv data and send headers
73
	# to browser to treat output as a file and download it
74
	$csv = new parseCSV();
75
	$csv->output (true, 'movies.csv', $array);
76
	----------------
77
78
79
*/
80
81
82
	/**
83
	 * Configuration
84
	 * - set these options with $object->var_name = 'value';
85
	 */
86
87
	# use first line/entry as field names
88
	var $heading = true;
89
90
	# override field names
91
	var $fields = array();
92
93
	# sort entries by this field
94
	var $sort_by = null;
95
	var $sort_reverse = false;
96
97
	# sort behavior passed to ksort/krsort functions
98
	# regular = SORT_REGULAR
99
	# numeric = SORT_NUMERIC
100
	# string  = SORT_STRING
101
	var $sort_type = null;
102
103
	# delimiter (comma) and enclosure (double quote)
104
	var $delimiter = ',';
105
	var $enclosure = '"';
106
107
	# basic SQL-like conditions for row matching
108
	var $conditions = null;
109
110
	# number of rows to ignore from beginning of data
111
	var $offset = null;
112
113
	# limits the number of returned rows to specified amount
114
	var $limit = null;
115
116
	# number of rows to analyze when attempting to auto-detect delimiter
117
	var $auto_depth = 15;
118
119
	# characters to ignore when attempting to auto-detect delimiter
120
	var $auto_non_chars = "a-zA-Z0-9\n\r";
121
122
	# preferred delimiter characters, only used when all filtering method
123
	# returns multiple possible delimiters (happens very rarely)
124
	var $auto_preferred = ",;\t.:|";
125
126
	# character encoding options
127
	var $convert_encoding = false;
128
	var $input_encoding = 'ISO-8859-1';
129
	var $output_encoding = 'ISO-8859-1';
130
131
	# used by unparse(), save(), and output() functions
132
	var $linefeed = "\r\n";
133
134
	# only used by output() function
135
	var $output_delimiter = ',';
136
	var $output_filename = 'data.csv';
137
138
	# keep raw file data in memory after successful parsing (useful for debugging)
139
	var $keep_file_data = false;
140
141
	/**
142
	 * Internal variables
143
	 */
144
145
	# current file
146
	var $file;
147
148
	# loaded file contents
149
	var $file_data;
150
151
	# error while parsing input data
152
	#  0 = No errors found. Everything should be fine :)
153
	#  1 = Hopefully correctable syntax error was found.
154
	#  2 = Enclosure character (double quote by default)
155
	#      was found in non-enclosed field. This means
156
	#      the file is either corrupt, or does not
157
	#      standard CSV formatting. Please validate
158
	#      the parsed data yourself.
159
	var $error = 0;
160
161
	# detailed error info
162
	var $error_info = array();
163
164
	# array of field values in data parsed
165
	var $titles = array();
166
167
	# two dimensional array of CSV data
168
	var $data = array();
169
170
171
	/**
172
	 * Constructor
173
	 * @param   input   CSV file or string
174
	 */
175
	function __construct ($input = null, $offset = null, $limit = null, $conditions = null) {
176
		if ( $offset !== null ) $this->offset = $offset;
177
		if ( $limit !== null ) $this->limit = $limit;
178
		if ( count($conditions) > 0 ) $this->conditions = $conditions;
179
		if ( !empty($input) ) $this->parse($input);
180
	}
181
182
    /**
183
     * @deprecated deprecated since version 7.6, PHP4 Style Constructors are deprecated and will be remove in 7.8, please update your code, use __construct instead
184
     */
185
    function parseCSV($input = null, $offset = null, $limit = null, $conditions = null){
186
        $deprecatedMessage = 'PHP4 Style Constructors are deprecated and will be remove in 7.8, please update your code';
187
        if(isset($GLOBALS['log'])) {
188
            $GLOBALS['log']->deprecated($deprecatedMessage);
189
        }
190
        else {
191
            trigger_error($deprecatedMessage, E_USER_DEPRECATED);
192
        }
193
        self::__construct($input, $offset, $limit, $conditions);
194
    }
195
196
197
198
	// ==============================================
199
	// ----- [ Main Functions ] ---------------------
200
	// ==============================================
201
202
	/**
203
	 * Parse CSV file or string
204
	 * @param   input   CSV file or string
205
	 * @return  nothing
206
	 */
207
	function parse ($input = null, $offset = null, $limit = null, $conditions = null) {
208
		if ( $input === null ) $input = $this->file;
209
		if ( !empty($input) ) {
210
			if ( $offset !== null ) $this->offset = $offset;
211
			if ( $limit !== null ) $this->limit = $limit;
212
			if ( count($conditions) > 0 ) $this->conditions = $conditions;
213
			if ( is_readable($input) ) {
214
				$this->data = $this->parse_file($input);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->parse_file($input) can also be of type false. However, the property $data is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
215
			} else {
216
				$this->file_data = &$input;
217
				$this->data = $this->parse_string();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->parse_string() can also be of type false. However, the property $data is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
218
			}
219
			if ( $this->data === false ) return false;
220
		}
221
		return true;
222
	}
223
224
	/**
225
	 * Save changes, or new file and/or data
226
	 * @param   file     file to save to
227
	 * @param   data     2D array with data
228
	 * @param   append   append current data to end of target CSV if exists
229
	 * @param   fields   field names
230
	 * @return  true or false
231
	 */
232
	function save ($file = null, $data = array(), $append = false, $fields = array()) {
233
		if ( empty($file) ) $file = &$this->file;
234
		$mode = ( $append ) ? 'at' : 'wt' ;
235
		$is_php = ( preg_match('/\.php$/i', $file) ) ? true : false ;
236
		return $this->_wfile($file, $this->unparse($data, $fields, $append, $is_php), $mode);
237
	}
238
239
	/**
240
	 * Generate CSV based string for output
241
	 * @param   filename    if specified, headers and data will be output directly to browser as a downloable file
242
	 * @param   data        2D array with data
243
	 * @param   fields      field names
244
	 * @param   delimiter   delimiter used to separate data
245
	 * @return  CSV data using delimiter of choice, or default
246
	 */
247
	function output ($filename = null, $data = array(), $fields = array(), $delimiter = null) {
248
		if ( empty($filename) ) $filename = $this->output_filename;
249
		if ( $delimiter === null ) $delimiter = $this->output_delimiter;
250
		$data = $this->unparse($data, $fields, null, null, $delimiter);
0 ignored issues
show
It seems like $delimiter can also be of type string; however, parseCSV::unparse() does only seem to accept object<field>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
251
		if ( $filename !== null ) {
252
			header('Content-type: application/csv');
253
			header('Content-Disposition: attachment; filename="'.$filename.'"');
254
			echo $data;
255
		}
256
		return $data;
257
	}
258
259
	/**
260
	 * Convert character encoding
261
	 * @param   input    input character encoding, uses default if left blank
262
	 * @param   output   output character encoding, uses default if left blank
263
	 * @return  nothing
264
	 */
265
	function encoding ($input = null, $output = null) {
266
		$this->convert_encoding = true;
267
		if ( $input !== null ) $this->input_encoding = $input;
268
		if ( $output !== null ) $this->output_encoding = $output;
269
	}
270
271
	/**
272
	 * Auto-Detect Delimiter: Find delimiter by analyzing a specific number of
273
	 * rows to determine most probable delimiter character
274
	 * @param   file           local CSV file
275
	 * @param   parse          true/false parse file directly
276
	 * @param   search_depth   number of rows to analyze
277
	 * @param   preferred      preferred delimiter characters
278
	 * @param   enclosure      enclosure character, default is double quote (").
279
	 * @return  delimiter character
280
	 */
281
	function auto ($file = null, $parse = true, $search_depth = null, $preferred = null, $enclosure = null) {
282
283
		if ( $file === null ) $file = $this->file;
284
		if ( empty($search_depth) ) $search_depth = $this->auto_depth;
285
		if ( $enclosure === null ) $enclosure = $this->enclosure;
286
                else $this->enclosure = $enclosure;
287
288
		if ( $preferred === null ) $preferred = $this->auto_preferred;
289
290
		if ( empty($this->file_data) ) {
291
			if ( $this->_check_data($file) ) {
292
				$data = &$this->file_data;
293
			} else return false;
294
		} else {
295
			$data = &$this->file_data;
296
		}
297
298
		$chars = array();
299
		$strlen = strlen($data);
300
		$enclosed = false;
301
		$n = 1;
302
		$to_end = true;
303
304
		// walk specific depth finding possible delimiter characters
305
		for ( $i=0; $i < $strlen; $i++ ) {
306
			$ch = $data{$i};
307
			$nch = ( isset($data{$i+1}) ) ? $data{$i+1} : false ;
308
			$pch = ( isset($data{$i-1}) ) ? $data{$i-1} : false ;
309
310
			// open and closing quotes
311
			if ( $ch == $enclosure ) {
312
				if ( !$enclosed || $nch != $enclosure ) {
313
					$enclosed = ( $enclosed ) ? false : true ;
314
				} elseif ( $enclosed ) {
315
					$i++;
316
				}
317
318
			// end of row
319
			} elseif ( ($ch == "\n" && $pch != "\r" || $ch == "\r") && !$enclosed ) {
320
				if ( $n >= $search_depth ) {
321
					$strlen = 0;
322
					$to_end = false;
323
				} else {
324
					$n++;
325
				}
326
327
			// count character
328
			} elseif (!$enclosed) {
329
				if ( !preg_match('/['.preg_quote($this->auto_non_chars, '/').']/i', $ch) ) {
330
					if ( !isset($chars[$ch][$n]) ) {
331
						$chars[$ch][$n] = 1;
332
					} else {
333
						$chars[$ch][$n]++;
334
					}
335
				}
336
			}
337
		}
338
339
		// filtering
340
		$depth = ( $to_end ) ? $n-1 : $n ;
341
		$filtered = array();
342
		foreach( $chars as $char => $value ) {
343
			if ( $match = $this->_check_count($char, $value, $depth, $preferred) ) {
344
				$filtered[$match] = $char;
345
			}
346
		}
347
348
		// capture most probable delimiter
349
		ksort($filtered);
350
		$this->delimiter = reset($filtered);
351
352
		// parse data
353
		if ( $parse ) $this->data = $this->parse_string();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->parse_string() can also be of type false. However, the property $data is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
354
355
		return $this->delimiter;
356
357
	}
358
359
360
	// ==============================================
361
	// ----- [ Core Functions ] ---------------------
362
	// ==============================================
363
364
	/**
365
	 * Read file to string and call parse_string()
366
	 * @param   file   local CSV file
367
	 * @return  2D array with CSV data, or false on failure
0 ignored issues
show
The doc-type 2D could not be parsed: Unknown type name "2D" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
368
	 */
369
	function parse_file ($file = null) {
370
		if ( $file === null ) $file = $this->file;
371
		if ( empty($this->file_data) ) $this->load_data($file);
372
		return ( !empty($this->file_data) ) ? $this->parse_string() : false ;
373
	}
374
375
	/**
376
	 * Parse CSV strings to arrays
377
	 * @param   data   CSV string
378
	 * @return  2D array with CSV data, or false on failure
0 ignored issues
show
The doc-type 2D could not be parsed: Unknown type name "2D" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
379
	 */
380
	function parse_string ($data = null) {
381
		if ( empty($data) ) {
382
			if ( $this->_check_data() ) {
383
				$data = &$this->file_data;
384
			} else return false;
385
		}
386
387
		$white_spaces = str_replace($this->delimiter, '', " \t\x0B\0");
388
389
		$rows = array();
390
		$row = array();
391
		$row_count = 0;
392
		$current = '';
393
		$head = ( !empty($this->fields) ) ? $this->fields : array() ;
394
		$col = 0;
395
		$enclosed = false;
396
		$was_enclosed = false;
397
		$strlen = strlen($data);
398
399
		// walk through each character
400
		for ( $i=0; $i < $strlen; $i++ ) {
401
			$ch = $data{$i};
402
			$nch = ( isset($data{$i+1}) ) ? $data{$i+1} : false ;
403
			$pch = ( isset($data{$i-1}) ) ? $data{$i-1} : false ;
404
405
			// open/close quotes, and inline quotes
406
			if ( $ch == $this->enclosure ) {
407
				if ( !$enclosed ) {
408
					if ( ltrim($current, $white_spaces) == '' ) {
409
						$enclosed = true;
410
						$was_enclosed = true;
411
					} else {
412
						$this->error = 2;
413
						$error_row = count($rows) + 1;
414
						$error_col = $col + 1;
415
						if ( !isset($this->error_info[$error_row.'-'.$error_col]) ) {
416
							$this->error_info[$error_row.'-'.$error_col] = array(
417
								'type' => 2,
418
								'info' => 'Syntax error found on row '.$error_row.'. Non-enclosed fields can not contain double-quotes.',
419
								'row' => $error_row,
420
								'field' => $error_col,
421
								'field_name' => (!empty($head[$col])) ? $head[$col] : null,
422
							);
423
						}
424
						$current .= $ch;
425
					}
426
				} elseif ($nch == $this->enclosure) {
427
					$current .= $ch;
428
					$i++;
429
				} elseif ( $nch != $this->delimiter && $nch != "\r" && $nch != "\n" ) {
430
					for ( $x=($i+1); isset($data{$x}) && ltrim($data{$x}, $white_spaces) == ''; $x++ ) {}
0 ignored issues
show
This for loop is empty and can be removed.

This check looks for for loops that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

Consider removing the loop.

Loading history...
431
					if ( $data{$x} == $this->delimiter ) {
432
						$enclosed = false;
433
						$i = $x;
434
					} else {
435
						if ( $this->error < 1 ) {
436
							$this->error = 1;
437
						}
438
						$error_row = count($rows) + 1;
439
						$error_col = $col + 1;
440
						if ( !isset($this->error_info[$error_row.'-'.$error_col]) ) {
441
							$this->error_info[$error_row.'-'.$error_col] = array(
442
								'type' => 1,
443
								'info' =>
444
									'Syntax error found on row '.(count($rows) + 1).'. '.
445
									'A single double-quote was found within an enclosed string. '.
446
									'Enclosed double-quotes must be escaped with a second double-quote.',
447
								'row' => count($rows) + 1,
448
								'field' => $col + 1,
449
								'field_name' => (!empty($head[$col])) ? $head[$col] : null,
450
							);
451
						}
452
						$current .= $ch;
453
						$enclosed = false;
454
					}
455
				} else {
456
					$enclosed = false;
457
				}
458
459
			// end of field/row
460
			} elseif ( ($ch == $this->delimiter || $ch == "\n" || $ch == "\r") && !$enclosed ) {
461
				$key = ( !empty($head[$col]) ) ? $head[$col] : $col ;
462
				$row[$key] = ( $was_enclosed ) ? $current : trim($current) ;
463
				$current = '';
464
				$was_enclosed = false;
465
				$col++;
466
467
				// end of row
468
				if ( $ch == "\n" || $ch == "\r" ) {
469
					if ( $this->_validate_offset($row_count) && $this->_validate_row_conditions($row, $this->conditions) ) {
470
						if ( $this->heading && empty($head) ) {
471
							$head = $row;
472
						} elseif ( empty($this->fields) || (!empty($this->fields) && (($this->heading && $row_count > 0) || !$this->heading)) ) {
473
							if ( !empty($this->sort_by) && !empty($row[$this->sort_by]) ) {
474
								if ( isset($rows[$row[$this->sort_by]]) ) {
475
									$rows[$row[$this->sort_by].'_0'] = &$rows[$row[$this->sort_by]];
476
									unset($rows[$row[$this->sort_by]]);
477
									for ( $sn=1; isset($rows[$row[$this->sort_by].'_'.$sn]); $sn++ ) {}
0 ignored issues
show
This for loop is empty and can be removed.

This check looks for for loops that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

Consider removing the loop.

Loading history...
478
									$rows[$row[$this->sort_by].'_'.$sn] = $row;
479
								} else $rows[$row[$this->sort_by]] = $row;
480
							} else $rows[] = $row;
481
						}
482
					}
483
					$row = array();
484
					$col = 0;
485
					$row_count++;
486
					if ( $this->sort_by === null && $this->limit !== null && count($rows) == $this->limit ) {
487
						$i = $strlen;
488
					}
489
					if ( $ch == "\r" && $nch == "\n" ) $i++;
490
				}
491
492
			// append character to current field
493
			} else {
494
				$current .= $ch;
495
			}
496
		}
497
		$this->titles = $head;
498
		if ( !empty($this->sort_by) ) {
499
			$sort_type = SORT_REGULAR;
500
			if ( $this->sort_type == 'numeric' ) {
501
				$sort_type = SORT_NUMERIC;
502
			} elseif ( $this->sort_type == 'string' ) {
503
				$sort_type = SORT_STRING;
504
			}
505
			( $this->sort_reverse ) ? krsort($rows, $sort_type) : ksort($rows, $sort_type) ;
506
			if ( $this->offset !== null || $this->limit !== null ) {
507
				$rows = array_slice($rows, ($this->offset === null ? 0 : $this->offset) , $this->limit, true);
508
			}
509
		}
510
		if ( !$this->keep_file_data ) {
511
			$this->file_data = null;
512
		}
513
		return $rows;
514
	}
515
516
	/**
517
	 * Create CSV data from array
518
	 * @param   data        2D array with data
519
	 * @param   fields      field names
520
	 * @param   append      if true, field names will not be output
521
	 * @param   is_php      if a php die() call should be put on the first
522
	 *                      line of the file, this is later ignored when read.
523
	 * @param   delimiter   field delimiter to use
524
	 * @return  CSV data (text string)
525
	 */
526
	function unparse ( $data = array(), $fields = array(), $append = false , $is_php = false, $delimiter = null) {
527
		if ( !is_array($data) || empty($data) ) $data = &$this->data;
528
		if ( !is_array($fields) || empty($fields) ) $fields = &$this->titles;
529
		if ( $delimiter === null ) $delimiter = $this->delimiter;
530
531
		$string = ( $is_php ) ? "<?php header('Status: 403'); die(' '); ?>".$this->linefeed : '' ;
532
		$entry = array();
533
534
		// create heading
535
		if ( $this->heading && !$append && !empty($fields) ) {
536
			foreach( $fields as $key => $value ) {
537
				$entry[] = $this->_enclose_value($value);
538
			}
539
			$string .= implode($delimiter, $entry).$this->linefeed;
540
			$entry = array();
541
		}
542
543
		// create data
544
		foreach( $data as $key => $row ) {
545
			foreach( $row as $field => $value ) {
546
				$entry[] = $this->_enclose_value($value);
547
			}
548
			$string .= implode($delimiter, $entry).$this->linefeed;
549
			$entry = array();
550
		}
551
552
		return $string;
553
	}
554
555
	/**
556
	 * Load local file or string
557
	 * @param   input   local CSV file
558
	 * @return  true or false
559
	 */
560
	function load_data ($input = null) {
561
		$data = null;
562
		$file = null;
563
		if ( $input === null ) {
564
			$file = $this->file;
565
		} elseif ( file_exists($input) ) {
566
			$file = $input;
567
		} else {
568
			$data = $input;
569
		}
570
		if ( !empty($data) || $data = $this->_rfile($file) ) {
571
			if ( $this->file != $file ) $this->file = $file;
572
			if ( preg_match('/\.php$/i', $file) && preg_match('/<\?.*?\?>(.*)/ims', $data, $strip) ) {
573
				$data = ltrim($strip[1]);
574
			}
575
			if ( $this->convert_encoding ) $data = iconv($this->input_encoding, $this->output_encoding, $data);
576
			if ( substr($data, -1) != "\n" ) $data .= "\n";
577
			$this->file_data = &$data;
578
			return true;
579
		}
580
		return false;
581
	}
582
583
584
	// ==============================================
585
	// ----- [ Internal Functions ] -----------------
586
	// ==============================================
587
588
	/**
589
	 * Validate a row against specified conditions
590
	 * @param   row          array with values from a row
591
	 * @param   conditions   specified conditions that the row must match
592
	 * @return  true of false
593
	 */
594
	function _validate_row_conditions ($row = array(), $conditions = null) {
595
		if ( !empty($row) ) {
596
			if ( !empty($conditions) ) {
597
				$conditions = (strpos($conditions, ' OR ') !== false) ? explode(' OR ', $conditions) : array($conditions) ;
598
				$or = '';
599
				foreach( $conditions as $key => $value ) {
600
					if ( strpos($value, ' AND ') !== false ) {
601
						$value = explode(' AND ', $value);
602
						$and = '';
603
						foreach( $value as $k => $v ) {
604
							$and .= $this->_validate_row_condition($row, $v);
605
						}
606
						$or .= (strpos($and, '0') !== false) ? '0' : '1' ;
607
					} else {
608
						$or .= $this->_validate_row_condition($row, $value);
609
					}
610
				}
611
				return (strpos($or, '1') !== false) ? true : false ;
612
			}
613
			return true;
614
		}
615
		return false;
616
	}
617
618
	/**
619
	 * Validate a row against a single condition
620
	 * @param   row          array with values from a row
621
	 * @param   condition   specified condition that the row must match
622
	 * @return  true of false
623
	 */
624
	function _validate_row_condition ($row, $condition) {
625
		$operators = array(
626
			'=', 'equals', 'is',
627
			'!=', 'is not',
628
			'<', 'is less than',
629
			'>', 'is greater than',
630
			'<=', 'is less than or equals',
631
			'>=', 'is greater than or equals',
632
			'contains',
633
			'does not contain',
634
		);
635
		$operators_regex = array();
636
		foreach( $operators as $value ) {
637
			$operators_regex[] = preg_quote($value, '/');
638
		}
639
		$operators_regex = implode('|', $operators_regex);
640
		if ( preg_match('/^(.+) ('.$operators_regex.') (.+)$/i', trim($condition), $capture) ) {
641
			$field = $capture[1];
642
			$op = $capture[2];
643
			$value = $capture[3];
644
			if ( preg_match('/^([\'\"]{1})(.*)([\'\"]{1})$/i', $value, $capture) ) {
645
				if ( $capture[1] == $capture[3] ) {
646
					$value = $capture[2];
647
					$value = str_replace("\\n", "\n", $value);
648
					$value = str_replace("\\r", "\r", $value);
649
					$value = str_replace("\\t", "\t", $value);
650
					$value = stripslashes($value);
651
				}
652
			}
653
			if ( array_key_exists($field, $row) ) {
654
				if ( ($op == '=' || $op == 'equals' || $op == 'is') && $row[$field] == $value ) {
655
					return '1';
656
				} elseif ( ($op == '!=' || $op == 'is not') && $row[$field] != $value ) {
657
					return '1';
658
				} elseif ( ($op == '<' || $op == 'is less than' ) && $row[$field] < $value ) {
659
					return '1';
660
				} elseif ( ($op == '>' || $op == 'is greater than') && $row[$field] > $value ) {
661
					return '1';
662
				} elseif ( ($op == '<=' || $op == 'is less than or equals' ) && $row[$field] <= $value ) {
663
					return '1';
664
				} elseif ( ($op == '>=' || $op == 'is greater than or equals') && $row[$field] >= $value ) {
665
					return '1';
666
				} elseif ( $op == 'contains' && preg_match('/'.preg_quote($value, '/').'/i', $row[$field]) ) {
667
					return '1';
668
				} elseif ( $op == 'does not contain' && !preg_match('/'.preg_quote($value, '/').'/i', $row[$field]) ) {
669
					return '1';
670
				} else {
671
					return '0';
672
				}
673
			}
674
		}
675
		return '1';
676
	}
677
678
	/**
679
	 * Validates if the row is within the offset or not if sorting is disabled
680
	 * @param   current_row   the current row number being processed
681
	 * @return  true of false
682
	 */
683
	function _validate_offset ($current_row) {
684
		if ( $this->sort_by === null && $this->offset !== null && $current_row < $this->offset ) return false;
685
		return true;
686
	}
687
688
	/**
689
	 * Enclose values if needed
690
	 *  - only used by unparse()
691
	 * @param   value   string to process
692
	 * @return  Processed value
693
	 */
694
	function _enclose_value ($value = null) {
695
		if ( $value !== null && $value != '' ) {
696
			$delimiter = preg_quote($this->delimiter, '/');
697
			$enclosure = preg_quote($this->enclosure, '/');
698
			if ( preg_match("/".$delimiter."|".$enclosure."|\n|\r/i", $value) || ($value{0} == ' ' || substr($value, -1) == ' ') ) {
699
				$value = str_replace($this->enclosure, $this->enclosure.$this->enclosure, $value);
700
				$value = $this->enclosure.$value.$this->enclosure;
701
			}
702
		}
703
		return $value;
704
	}
705
706
	/**
707
	 * Check file data
708
	 * @param   file   local filename
709
	 * @return  true or false
710
	 */
711
	function _check_data ($file = null) {
712
		if ( empty($this->file_data) ) {
713
			if ( $file === null ) $file = $this->file;
714
			return $this->load_data($file);
715
		}
716
		return true;
717
	}
718
719
720
	/**
721
	 * Check if passed info might be delimiter
722
	 *  - only used by find_delimiter()
723
	 * @return  special string used for delimiter selection, or false
724
	 */
725
	function _check_count ($char, $array, $depth, $preferred) {
726
		if ( $depth == count($array) ) {
727
			$first = null;
728
			$equal = null;
729
			$almost = false;
730
			foreach( $array as $key => $value ) {
731
				if ( $first == null ) {
732
					$first = $value;
733
				} elseif ( $value == $first && $equal !== false) {
734
					$equal = true;
735
				} elseif ( $value == $first+1 && $equal !== false ) {
736
					$equal = true;
737
					$almost = true;
738
				} else {
739
					$equal = false;
740
				}
741
			}
742
			if ( $equal ) {
743
				$match = ( $almost ) ? 2 : 1 ;
744
				$pref = strpos($preferred, $char);
745
				$pref = ( $pref !== false ) ? str_pad($pref, 3, '0', STR_PAD_LEFT) : '999' ;
746
				return $pref.$match.'.'.(99999 - str_pad($first, 5, '0', STR_PAD_LEFT));
747
			} else return false;
748
		}
749
	}
750
751
	/**
752
	 * Read local file
753
	 * @param   file   local filename
754
	 * @return  Data from file, or false on failure
755
	 */
756
	function _rfile ($file = null) {
757
		if ( is_readable($file) ) {
758
			if ( !($fh = fopen($file, 'r')) ) return false;
759
			$data = fread($fh, filesize($file));
760
			fclose($fh);
761
			return $data;
762
		}
763
		return false;
764
	}
765
766
	/**
767
	 * Write to local file
768
	 * @param   file     local filename
769
	 * @param   string   data to write to file
770
	 * @param   mode     fopen() mode
771
	 * @param   lock     flock() mode
772
	 * @return  true or false
773
	 */
774
	function _wfile ($file, $string = '', $mode = 'wb', $lock = 2) {
775
		if ( $fp = fopen($file, $mode) ) {
776
			flock($fp, $lock);
777
			$re = fwrite($fp, $string);
778
			$re2 = fclose($fp);
779
			if ( $re != false && $re2 != false ) return true;
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $re of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
780
		}
781
		return false;
782
	}
783
784
}
785
786
?>
787