mirror of
https://github.com/salesagility/SuiteCRM.git
synced 2024-11-22 07:52:36 +00:00
486 lines
14 KiB
PHP
Executable File
486 lines
14 KiB
PHP
Executable File
<?php
|
|
if (!defined('sugarEntry') || !sugarEntry) {
|
|
die('Not A Valid Entry Point');
|
|
}
|
|
|
|
/**
|
|
*
|
|
* SugarCRM Community Edition is a customer relationship management program developed by
|
|
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
|
|
*
|
|
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
|
|
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU Affero General Public License version 3 as published by the
|
|
* Free Software Foundation with the addition of the following permission added
|
|
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
|
|
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
|
|
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License along with
|
|
* this program; if not, see http://www.gnu.org/licenses or write to the Free
|
|
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA.
|
|
*
|
|
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
|
|
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
|
|
*
|
|
* The interactive user interfaces in modified source and object code versions
|
|
* of this program must display Appropriate Legal Notices, as required under
|
|
* Section 5 of the GNU Affero General Public License version 3.
|
|
*
|
|
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
|
* these Appropriate Legal Notices must retain the display of the "Powered by
|
|
* SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
|
|
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
|
|
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
|
|
*/
|
|
|
|
/**
|
|
|
|
* Description: Class to handle processing an import file
|
|
* Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
|
|
* All Rights Reserved.
|
|
*/
|
|
|
|
require_once('modules/Import/CsvAutoDetect.php');
|
|
require_once('modules/Import/sources/ImportDataSource.php');
|
|
|
|
#[\AllowDynamicProperties]
|
|
class ImportFile extends ImportDataSource
|
|
{
|
|
/**
|
|
* Stores whether or not we are deleting the import file in the destructor
|
|
*/
|
|
private $_deleteFile;
|
|
|
|
/**
|
|
* File pointer returned from fopen() call
|
|
*/
|
|
private $_fp = false;
|
|
|
|
/**
|
|
* True if the csv file has a header row.
|
|
*/
|
|
private $_hasHeader = false;
|
|
|
|
/**
|
|
* True if the csv file has a header row.
|
|
*/
|
|
private $_detector = null;
|
|
|
|
/**
|
|
* CSV date format
|
|
*/
|
|
private $_date_format = false;
|
|
|
|
/**
|
|
* CSV time format
|
|
*/
|
|
private $_time_format = false;
|
|
|
|
/**
|
|
* The import file map that this import file inherits properties from.
|
|
*/
|
|
private $_importFile = null;
|
|
|
|
/**
|
|
* Delimiter string we are using (i.e. , or ;)
|
|
*/
|
|
private $_delimiter;
|
|
|
|
/**
|
|
* Enclosure string we are using (i.e. ' or ")
|
|
*/
|
|
private $_enclosure;
|
|
|
|
/**
|
|
* File encoding, used to translate the data into UTF-8 for display and import
|
|
*/
|
|
private $_encoding;
|
|
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param string $filename
|
|
* @param string $delimiter
|
|
* @param string $enclosure
|
|
* @param bool $deleteFile
|
|
*/
|
|
public function __construct($filename, $delimiter = ',', $enclosure = '', $deleteFile = true, $checkUploadPath = true)
|
|
{
|
|
if (!is_file($filename) || !is_readable($filename)) {
|
|
return false;
|
|
}
|
|
|
|
if ($checkUploadPath && UploadStream::path($filename) == null) {
|
|
$GLOBALS['log']->fatal("ImportFile detected attempt to access to the following file not within the sugar upload dir: $filename");
|
|
return null;
|
|
}
|
|
|
|
// turn on auto-detection of line endings to fix bug #10770
|
|
ini_set('auto_detect_line_endings', '1');
|
|
|
|
$this->_fp = sugar_fopen($filename, 'r');
|
|
$this->_sourcename = $filename;
|
|
$this->_deleteFile = $deleteFile;
|
|
$this->_delimiter = (empty($delimiter) ? ',' : $delimiter);
|
|
if ($this->_delimiter == '\t') {
|
|
$this->_delimiter = "\t";
|
|
}
|
|
$this->_enclosure = (empty($enclosure) ? '' : trim($enclosure));
|
|
|
|
// Autodetect does setFpAfterBOM()
|
|
$this->_encoding = $this->autoDetectCharacterSet();
|
|
}
|
|
|
|
/**
|
|
* Remove the BOM (Byte Order Mark) from the beginning of the import row if it exists
|
|
* @return void
|
|
*/
|
|
private function setFpAfterBOM()
|
|
{
|
|
if ($this->_fp === false) {
|
|
return;
|
|
}
|
|
|
|
rewind($this->_fp);
|
|
$bomCheck = fread($this->_fp, 3);
|
|
if ($bomCheck != pack("CCC", 0xef, 0xbb, 0xbf)) {
|
|
rewind($this->_fp);
|
|
}
|
|
}
|
|
/**
|
|
* Destructor
|
|
*
|
|
* Deletes $_importFile if $_deleteFile is true
|
|
*/
|
|
public function __destruct()
|
|
{
|
|
if ($this->_deleteFile && $this->fileExists()) {
|
|
fclose($this->_fp);
|
|
//Make sure the file exists before unlinking
|
|
if (file_exists($this->_sourcename)) {
|
|
unlink($this->_sourcename);
|
|
}
|
|
}
|
|
|
|
ini_restore('auto_detect_line_endings');
|
|
}
|
|
|
|
/**
|
|
* This is needed to prevent unserialize vulnerability
|
|
*/
|
|
public function __wakeup()
|
|
{
|
|
// clean all properties
|
|
foreach (get_object_vars($this) as $k => $v) {
|
|
$this->$k = null;
|
|
}
|
|
throw new Exception("Not a serializable object");
|
|
}
|
|
|
|
/**
|
|
* Returns true if the filename given exists and is readable
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function fileExists()
|
|
{
|
|
return !$this->_fp ? false : true;
|
|
}
|
|
|
|
/**
|
|
* Gets the next row from $_importFile
|
|
*
|
|
* @return array current row of file
|
|
*/
|
|
public function getNextRow()
|
|
{
|
|
$this->_currentRow = false;
|
|
|
|
if (!$this->fileExists()) {
|
|
return false;
|
|
}
|
|
|
|
// explode on delimiter instead if enclosure is an empty string
|
|
if (empty($this->_enclosure)) {
|
|
$row = explode($this->_delimiter, rtrim(fgets($this->_fp, 8192), "\r\n"));
|
|
if ($row !== false && !(count($row) == 1 && trim($row[0]) == '')) {
|
|
$this->_currentRow = $row;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
$row = fgetcsv($this->_fp, 8192, $this->_delimiter, $this->_enclosure);
|
|
if ($row !== false && $row != array(null)) {
|
|
$this->_currentRow = $row;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
global $locale;
|
|
foreach ($this->_currentRow as $key => $value) {
|
|
// If encoding is set, convert all values from it
|
|
if (!empty($this->_encoding)) {
|
|
// Convert all values to UTF-8 for display and import purposes
|
|
$this->_currentRow[$key] = $locale->translateCharset($value, $this->_encoding);
|
|
}
|
|
|
|
$this->_currentRow[$key] = securexss($value);
|
|
|
|
// Convert all line endings to the same style as PHP_EOL
|
|
// Use preg_replace instead of str_replace as str_replace may cause extra lines on Windows
|
|
$this->_currentRow[$key] = preg_replace("[\r\n|\n|\r]", PHP_EOL, (string) $this->_currentRow[$key]);
|
|
}
|
|
|
|
$this->_rowsCount++;
|
|
|
|
return $this->_currentRow;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of fields in the current row
|
|
*
|
|
* @return int count of fiels in the current row
|
|
*/
|
|
public function getFieldCount()
|
|
{
|
|
return is_countable($this->_currentRow) ? count($this->_currentRow) : 0;
|
|
}
|
|
|
|
/**
|
|
* Determine the number of lines in this file.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getNumberOfLinesInfile()
|
|
{
|
|
$lineCount = 0;
|
|
|
|
if ($this->_fp) {
|
|
rewind($this->_fp);
|
|
while (!feof($this->_fp)) {
|
|
if (fgets($this->_fp) !== false) {
|
|
$lineCount++;
|
|
}
|
|
}
|
|
//Reset the fp to after the bom if applicable.
|
|
$this->setFpAfterBOM();
|
|
}
|
|
|
|
return $lineCount;
|
|
}
|
|
|
|
//TODO: Add auto detection for field delim and qualifier properteis.
|
|
public function autoDetectCSVProperties()
|
|
{
|
|
// defaults
|
|
$this->_delimiter = ",";
|
|
$this->_enclosure = '"';
|
|
|
|
$this->_detector = new CsvAutoDetect($this->_sourcename);
|
|
|
|
$delimiter = $enclosure = false;
|
|
|
|
$ret = $this->_detector->getCsvSettings($delimiter, $enclosure);
|
|
if ($ret) {
|
|
$this->_delimiter = $delimiter;
|
|
$this->_enclosure = $enclosure;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function getFieldDelimeter()
|
|
{
|
|
return $this->_delimiter;
|
|
}
|
|
|
|
public function getFieldEnclosure()
|
|
{
|
|
return $this->_enclosure;
|
|
}
|
|
|
|
public function autoDetectCharacterSet()
|
|
{
|
|
// If encoding is already detected, just return it
|
|
if (!empty($this->_encoding)) {
|
|
return $this->_encoding;
|
|
}
|
|
|
|
// Move file pointer to start
|
|
$this->setFpAfterBOM();
|
|
|
|
global $locale;
|
|
$user_charset = $locale->getExportCharset();
|
|
$system_charset = $locale->default_export_charset;
|
|
$other_charsets = 'UTF-8, UTF-7, ASCII, CP1252, EUC-JP, SJIS, eucJP-win, SJIS-win, JIS, ISO-2022-JP';
|
|
$detectable_charsets = "UTF-8, {$user_charset}, {$system_charset}, {$other_charsets}";
|
|
|
|
// Bug 26824 - mb_detect_encoding() thinks CP1252 is IS0-8859-1, so use that instead in the encoding list passed to the function
|
|
$detectable_charsets = str_replace('CP1252', 'ISO-8859-1', $detectable_charsets);
|
|
|
|
// If we are able to detect encoding
|
|
if (function_exists('mb_detect_encoding')) {
|
|
// Retrieve a sample of data set
|
|
$text = '';
|
|
|
|
// Read 10 lines from the file and put them all together in a variable
|
|
$i = 0;
|
|
while ($i < 10 && $temp = fgets($this->_fp, 8192)) {
|
|
$text .= $temp;
|
|
$i++;
|
|
}
|
|
|
|
// If we picked any text, try to detect charset
|
|
if (strlen($text) > 0) {
|
|
$charset_for_import = mb_detect_encoding($text, $detectable_charsets);
|
|
}
|
|
}
|
|
|
|
// If we couldn't detect the charset, set it to default export/import charset
|
|
if (empty($charset_for_import)) {
|
|
$charset_for_import = $locale->getExportCharset();
|
|
}
|
|
|
|
// Reset the fp to after the bom if applicable.
|
|
$this->setFpAfterBOM();
|
|
|
|
return $charset_for_import;
|
|
}
|
|
|
|
public function getDateFormat()
|
|
{
|
|
if ($this->_detector) {
|
|
$this->_date_format = $this->_detector->getDateFormat();
|
|
}
|
|
|
|
return $this->_date_format;
|
|
}
|
|
|
|
public function getTimeFormat()
|
|
{
|
|
if ($this->_detector) {
|
|
$this->_time_format = $this->_detector->getTimeFormat();
|
|
}
|
|
|
|
return $this->_time_format;
|
|
}
|
|
|
|
public function setHeaderRow($hasHeader)
|
|
{
|
|
$this->_hasHeader = $hasHeader;
|
|
}
|
|
|
|
public function hasHeaderRow($autoDetect = true)
|
|
{
|
|
if ($autoDetect) {
|
|
if (!isset($_REQUEST['import_module'])) {
|
|
return false;
|
|
}
|
|
|
|
$module = $_REQUEST['import_module'];
|
|
|
|
$ret = false;
|
|
$heading = false;
|
|
|
|
if ($this->_detector) {
|
|
$ret = $this->_detector->hasHeader($heading, $module, $this->_encoding);
|
|
}
|
|
|
|
if ($ret) {
|
|
$this->_hasHeader = $heading;
|
|
}
|
|
}
|
|
return $this->_hasHeader;
|
|
}
|
|
|
|
public function setImportFileMap($map)
|
|
{
|
|
$this->_importFile = $map;
|
|
$importMapProperties = array('_delimiter' => 'delimiter','_enclosure' => 'enclosure', '_hasHeader' => 'has_header');
|
|
//Inject properties from the import map
|
|
foreach ($importMapProperties as $k => $v) {
|
|
$this->$k = $map->$v;
|
|
}
|
|
}
|
|
|
|
//Begin Implementation for SPL's Iterator interface
|
|
public function key()
|
|
{
|
|
return $this->_rowsCount;
|
|
}
|
|
|
|
public function current()
|
|
{
|
|
return $this->_currentRow;
|
|
}
|
|
|
|
public function next()
|
|
{
|
|
$this->getNextRow();
|
|
}
|
|
|
|
public function valid()
|
|
{
|
|
return $this->_currentRow !== false;
|
|
}
|
|
|
|
public function rewind()
|
|
{
|
|
$this->setFpAfterBOM();
|
|
//Load our first row
|
|
$this->getNextRow();
|
|
}
|
|
|
|
public function getTotalRecordCount()
|
|
{
|
|
$totalCount = $this->getNumberOfLinesInfile();
|
|
if ($this->hasHeaderRow(false) && $totalCount > 0) {
|
|
$totalCount--;
|
|
}
|
|
return $totalCount;
|
|
}
|
|
|
|
public function loadDataSet($totalItems = 0)
|
|
{
|
|
$currentLine = 0;
|
|
$this->_dataSet = array();
|
|
$this->rewind();
|
|
//If there's a header don't include it.
|
|
if ($this->hasHeaderRow(false)) {
|
|
$this->next();
|
|
}
|
|
|
|
while ($this->valid() && $totalItems > (is_countable($this->_dataSet) ? count($this->_dataSet) : 0)) {
|
|
if ($currentLine >= $this->_offset) {
|
|
$this->_dataSet[] = $this->_currentRow;
|
|
}
|
|
$this->next();
|
|
$currentLine++;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getHeaderColumns()
|
|
{
|
|
$this->rewind();
|
|
if ($this->hasHeaderRow(false)) {
|
|
return $this->_currentRow;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|