*/ /** * Definicje bledow importu */ define( "IMPORT_NO_CONFIG", "Plik configuracyjny exportu nie istnieje." ); define( "IMPORT_NO_FILE", "Plik z danymi nie istnieje lub nie można go doczytać." ); /** * Klasa obslugi import danych * * @package stImportExportPlugin * @subpackage libs */ class stPropelImporter implements stPropelImportExportInterface { /** * kolejnosc danych w pliku * @var array */ var $header_order = array(); /** * Sciezka do pliku z danymi * @var string */ var $filepath = null; /** * Nazwa modelu podstawowego * @var string */ var $model = ''; /** * Konfiguracja modulu * @var array */ var $config = array(); /** * Limit danych ekportowanych/importowanych danych * @var integer */ var $limit = 5; var $hard_limit = null; /** * uchwyt pliku z danymi * @var mixed */ var $fh = null; /** * Unikatowa nazwa konvertera * @var string */ var $converter = ''; var $culture = null; protected static $current_data = array(); protected static $current_key = ''; protected $logger = null; protected $primaryKey = null; protected $compositePrimaryKey = false; protected $requireOnCreateFields = array(); protected $requireFields = array(); protected $i18nCatalogue = null; /** * Konstruktor klasy, jako paramtry nalezy podac model bazowy, * liste pol do eksportu/importu, w przypadku importu danych * nalezy podac dodatkowo nazwe pliku * * @param string $model * @param array $fields * @param string file */ public function __construct($model, array $fields, $filepath) { $this->filepath = $filepath; $this->model = $model; $this->config = $fields; $this->culture = stLanguage::getOptLanguage(); $this->logger = new stImportExportLog(sfConfig::get('sf_log_dir').DIRECTORY_SEPARATOR.'import_'.$model.'.log'); foreach ($this->config['fields'] as $name => $params) { if (isset($params['require']) || isset($params['require_with_fields'])) { $this->requireFields[] = $name; } if (isset($params['require_on_create'])) { $this->requireOnCreateFields[] = $name; } } } public function setI18nCatalogue($i18nCatalogue) { $this->i18nCatalogue = $i18nCatalogue; } public function getFilePath() { return $this->filepath; } /** * Zwraca konfiguracja importu * * @return array */ public function getConfig() { return $this->config; } public function setCulture($culture) { $this->culture = $culture; } /** * glowna petla importu, dane przetwarzane sa od offsetu * zwraca kolejny offset * * @param integer $offset * @return integer */ public function doProcess($offset = 0) { // odczytaj plik $this->loadFile(); // wczytaj plik $this->readHeader(); //odczytaj wiersz naglowka $this->readHeaderRow(); //pomin wczesniej odczytane dane $this->skipToData($offset); // odczytaj dane $this->readData(); // zwroc aktualny offset return $this->getOffset(); } /** * zwraca libcze krokow potrzebnych do odczytania calego pliku * * @return integer */ public function getDataCount() { return 0; } /** * Odczytuje naglowek pliu, zwraca true w przypadku powowdzeni, * false w przypadku bledu * * @return boolean */ protected function readHeader() { return true; } /** * Odczytuje wiersz naglowka , zwraca true w przypadku powowdzeni, * false w przypadku bledu * * @return boolean */ protected function readHeaderRow() { return true; } /** * odczytuje jeden wiersz/blok danych, zwraca true jezeli operacja * zakonczyla sie powodzeniem * * @return boolean */ protected function readRow() { return true; } /** * Zwraca aktualne polozenie w pliku, potrzebne do wykonania * kolejnego kroku importu * * @return integer */ protected function getOffset() { return 1; } /** * Pobiera aktualnie dodawany obiekt z wykorzytaniem klucza * * @param mixed $key_value * @return object */ protected function getObject($key_value = null, $culture = null) { if (!$culture) $culter = $this->culture; // wyszukaj obiekt spełniający podane kryteria if (is_array($key_value)) { $object = call_user_func_array($this->model.'Peer::retrieveByPK', $key_value); } else { $c = new Criteria(); $c->add(constant("{$this->model}Peer::".strtoupper($this->config['primary_key'])),$key_value); $object = call_user_func($this->model.'Peer::doSelectOne',$c); } // jezeli nie znaleziono takiego obiektu to go stwórz if (!$object) { $object = new $this->model; } if (method_exists($object,'setCulture')) { $object->setCulture($culture); } return $object; } protected function loadPrimaryKey(array $data) { $this->compositePrimaryKey = is_array($this->config['primary_key']); if ($this->compositePrimaryKey && (empty($data[$this->config['primary_key'][0]]) || empty($data[$this->config['primary_key'][1]])) || !$this->compositePrimaryKey && empty($data[$this->config['primary_key']])) { $this->logger->add(null, sfContext::getInstance()->getI18n()->__('Brak wymaganego pola %s% lub jego wartość jest niepoprawna.', array('%s%' => $this->getUserName($this->config['primary_key'])), 'stImportExportBackend'), stImportExportLog::$WARNING); return false; } // set logger current key; if ($this->compositePrimaryKey) { $this->primaryKey = array( $data[$this->config['primary_key'][0]], $data[$this->config['primary_key'][1]], ); $this->logger->setCurrentKey(implode("_", $this->primaryKey)); } else { $this->primaryKey = $data[$this->config['primary_key']]; $this->logger->setCurrentKey($this->primaryKey); } return true; } /** * Wykonuje import danych do bazy * * @param array */ protected function processData($data = array()) { if (!$this->testRequirements($data)) { return false; } try { if (!$this->validateData($data)) { return false; } } catch (Exception $e) { $this->logger->add(null, $e->getMessage(), stImportExportLog::$FATAL); return false; } // sprawdza czy podano klucz po ktorym zosanie wyszukany rekord do aktualizacji // znajduje lub tworzy odpowiedni wpis try { if (method_exists($this->config['class'], 'primaryKeyMapping')) { $primaryKey = call_user_func(array($this->config['class'], 'primaryKeyMapping'), $this->primaryKey); } else { $primaryKey = $this->primaryKey; } $object = $this->getObject($primaryKey,$this->culture); } catch (Exception $e) { $this->logger->add(null, $e->getMessage(), stImportExportLog::$FATAL); return false; } // jezeli ustawiono status usuniecia nie aktualizuj i usun objekt if (isset($data['import_status']) && mb_strtolower($data['import_status'],'utf-8') == 'd') { if (!$object->isNew()) { $object->delete(); } } else { if ($object->isNew() && !$this->testOnCreateRequirements($data)) { return false; } $changed = array(); $md5hash = !$this->compositePrimaryKey ? ExportMd5HashPeer::retrieveByModelId($object->getId(), $this->model) : null; // Uzupelnia dane dla obiektu na podstawie modelu foreach ($this->config['fields'] as $func_name => $Peer) { // jezeli dane nie sa ustawione, przypisz domyslne if (!isset($data[$func_name]) && isset($this->config['fields'][$func_name]['default'])) { $data[$func_name] = $this->config['fields'][$func_name]['default']; } // jezeli informacja wystepuje w danych zapisuje ja w obiekcie if (isset($data[$func_name])) { $changed[$func_name] = null !== $md5hash && isset($Peer['md5hash']) && $Peer['md5hash'] ? !$md5hash->runDataHashCheck($func_name, $data[$func_name], true) : true; if ($changed[$func_name]) { // pobiera rzeczywista nazwe funkcji odpowiedzialnej ustawienie danych $real_func_name = 'set'. sfInflector::camelize($func_name); if (isset($Peer['method'])) $real_func_name = $Peer['method']; // jezeli metoda istnieje zostanie ona wykonana if (!isset($Peer['class'])) { try { if (isset($Peer['custom_method']) && $Peer['custom_method']) { call_user_func($this->config['default_class'].'::'.$real_func_name, $object, $data[$func_name],$this->logger, $data); } else { $object->$real_func_name($data[$func_name], $this->logger); } } catch (Exception $e) { $this->logger->add(null, $e->getMessage(), stImportExportLog::$FATAL); if (null !== $md5hash) { $md5hash->restoreMd5Hash($func_name); } return false; } } } } } // zapisuje wartosci try { $object->save(); } catch (Exception $e) { $this->logger->add(null, $e->getMessage(), stImportExportLog::$FATAL); return false; } // Uzupelnia dane z zewnetrznych modeli foreach ($this->config['fields'] as $func_name => $Peer) { // jezeli informacja wystepuje w danych wykonuje odpowiednia funkcje if (isset($data[$func_name]) && $changed[$func_name]) { // pobiera rzeczywista nazwe funkcji odpowiedzialnej ustawienie danych $real_func_name = 'set'. sfInflector::camelize($func_name); if (isset($Peer['method'])) $real_func_name = $Peer['method']; // jezeli metoda istnieje zostanie ona wykonana if (isset($Peer['class'])) { $Peer = $Peer['class']; } else { $Peer = $this->config['default_class']; } if (is_callable($Peer.'::'.$real_func_name)) { try { call_user_func($Peer.'::'.$real_func_name, $object, $data[$func_name],$this->logger, $data); } catch (Exception $e) { $this->logger->add(null, $e->getMessage(), stImportExportLog::$FATAL); if (null !== $md5hash) { $md5hash->restoreMd5Hash($func_name); } } } } } try { stEventDispatcher::getInstance()->notify(new sfEvent($this, 'stImportExport.Import_'.$this->model, array('modelInstance'=>$object))); if (isset($this->config['default_class']) && is_callable($this->config['default_class'].'::preSave')) { call_user_func($this->config['default_class'].'::preSave', $object, $this->logger); } $object->save(); if (null !== $md5hash) { if ($md5hash->isNew()) { $md5hash->setId($object->getId()); } if ($md5hash->isColumnModified(ExportMd5HashPeer::MD5HASH)) { $md5hash->save(); } } } catch (Exception $e) { $this->logger->add(null, $e->getMessage(), stImportExportLog::$FATAL); } } } /** * Sprawdza wymagania dla pól * * @param array $data * @return void */ public function testRequirements(array $data) { $ret = true; $availableFields = array_keys($data); foreach ($this->requireFields as $name) { $params = $this->config['fields'][$name]; $required = !isset($params['require_with_fields']) || !empty(array_intersect($params['require_with_fields'], $availableFields)); if ($required && (!isset($data[$name]) || empty(trim($data[$name])))) { $this->logger->add(null, sfContext::getInstance()->getI18n()->__('Brak wymaganego pola: %s%', array('%s%'=>$this->getUserName($name)), 'stImportExportBackend'), stImportExportLog::$WARNING); $ret = false; } } return $ret; } /** * Sprawdza wymagania dla pól podczas tworzenia nowego rekordu * @param array $data Dane * @return bool */ public function testOnCreateRequirements(array $data) { $ret = true; foreach ($this->requireOnCreateFields as $name) { if (!isset($data[$name]) || empty(trim($data[$name]))) { $this->logger->add(null, sfContext::getInstance()->getI18n()->__('Brak wymaganego pola: %s%', array('%s%'=>$this->getUserName($name)), 'stImportExportBackend'), stImportExportLog::$WARNING); $ret = false; } } return $ret; } /** * Waliduje poszczególne pola * * @param array $data Dane * @return bool */ public function validateData(array $data) { $isValid = true; foreach ($this->config['fields'] as $name => $params) { if (isset($data[$name])) { $type = isset($params['type']) ? $params['type'] : 'string'; switch ($type) { case "string": if (!is_string($data[$name])) { $this->logger->add(null, sfContext::getInstance()->getI18n()->__('Niepoprawna wartość pola ', array(), 'stImportExportBackend').$this->getUserName($name).', "'.$data[$name].'" '.sfContext::getInstance()->getI18n()->__('nie jest ciągiem znaków', array(), 'stImportExportBackend'), stImportExportLog::$NOTICE); $isValid = false; } break; case "double": if (((!preg_match('/^-?\d+(\.\d+)?$/', $data[$name])) || substr_count($data[$name], '.') != 1) &&((!preg_match('/^-?\d+(\.\d+)?$/', $data[$name])) || ((float) $data[$name] != (int) $data[$name]))) { $this->logger->add(null, sfContext::getInstance()->getI18n()->__('Niepoprawna wartość pola ', array(), 'stImportExportBackend').$this->getUserName($name).', "'.$data[$name].'" '.sfContext::getInstance()->getI18n()->__('nie jest liczbą', array(), 'stImportExportBackend'), stImportExportLog::$NOTICE); $isValid = false; } break; case "bool": if (!is_numeric($data[$name]) && !$data[$name] != 0 && !$data[$name] != 1) { $this->logger->add(null, sfContext::getInstance()->getI18n()->__('Niepoprawna wartość pola ', array(), 'stImportExportBackend').$this->getUserName($name).', "'.$data[$name].'" '.sfContext::getInstance()->getI18n()->__('nie jest 1 lub 0', array(), 'stImportExportBackend'), stImportExportLog::$NOTICE); $isValid = false; } break; case "boolean": if (!is_numeric($data[$name]) && !$data[$name] != 0 && !$data[$name] != 1) { $this->logger->add(null, sfContext::getInstance()->getI18n()->__('Niepoprawna wartość pola ', array(), 'stImportExportBackend').$this->getUserName($name).', "'.$data[$name].'" '.sfContext::getInstance()->getI18n()->__('nie jest 1 lub 0', array(), 'stImportExportBackend'), stImportExportLog::$NOTICE); $isValid = false; } break; case "int": if ((!preg_match('/^-?\d+(\.\d+)?$/', $data[$name])) || ((float) $data[$name] != (int) $data[$name])) { $this->logger->add(null, sfContext::getInstance()->getI18n()->__('Niepoprawna wartość pola ', array(), 'stImportExportBackend').$this->getUserName($name).', "'.$data[$name].'" '.sfContext::getInstance()->getI18n()->__('nie jest liczbą całkowitą', array(), 'stImportExportBackend'), stImportExportLog::$NOTICE); $isValid = false; } break; case "integer": if ((!preg_match('/^-?\d+(\.\d+)?$/', $data[$name])) || ((float) $data[$name] != (int) $data[$name])) { $this->logger->add(null, sfContext::getInstance()->getI18n()->__('Niepoprawna wartość pola ', array(), 'stImportExportBackend').$this->getUserName($name).', "'.$data[$name].'" '.sfContext::getInstance()->getI18n()->__('nie jest liczbą całkowitą', array(), 'stImportExportBackend') ,stImportExportLog::$NOTICE); $isValid = false; } break; case "custom": $call = $this->config['default_class'] ? $this->config['default_class'] : $this->model."Peer"; if (isset($params['class'])) { $call = $params['class']; } if (is_callable($call."::".sfInflector::camelize("import_validate_".$name))) { if(!call_user_func($call."::".sfInflector::camelize("import_validate_".$name), $data[$name], $this->primaryKey, $data, $this)) { $isValid = false; } } break; default: if (!is_string($data[$name])) unset($data[$name]); break; } } } return $isValid; } /** * odczytuje dane z pliku, zwraca liczbe odczytanych danych * * @return integer */ protected function readData() { $readed = 0; // czytaj az do przekroczenia limitu lub gdy plik sie skonczy while ($this->readRow() && $readed<$this->limit) { $readed++; } return $readed; } /** * Przeskakuje do odpowiedniego miejsca w czytanym pliku * * @param integer $offset * @return boolean */ protected function skipToData($offset = 0) { return true; } /** * Funkcja otwiera plik, kod otwierania musi znalezc sie w eksporterze * do konkretnego formatu, w przypadku powodzenia zwraca true * * @return boolean */ protected function loadFile() { return true; } /** * Funkcja zamyka plik, kod zamykania musi znalezc sie w eksporterze * do konkretnego formatu, w przypadku powodzenia zwraca true * * @return boolean */ protected function closeFile() { return true; } public function validateFile(array &$errors = null) { return false; } public function setLimit($limit = 20) { if (is_integer($limit) && $limit>0) { $this->limit = $limit; if ($this->hard_limit && $this->limit > $this->hard_limit) { $this->limit = $this->hard_limit; } } } public function removeUserName($data = array()) { $tmp = array(); foreach ($data as $value) { $new_value_array = explode('::',$value); if (isset($new_value_array[1])) $new_value = $new_value_array[1]; else $new_value = $value; $tmp[] = $new_value; } return $tmp; } public function getUserName($name) { if (is_array($name)) { return implode("_", array_map(function($name) { return $this->translateFieldName($name); }, $name)); } return $this->translateFieldName($name); } protected function translateFieldName($name) { if (isset($this->config['fields'][$name]['name'])) { $field = $this->config['fields'][$name]; $i18nCatalogue = $this->i18nCatalogue; if (isset($field['i18n'])) { $i18nCatalogue = $field['i18n']; } elseif (isset($field['i18n_file'])) { $i18nCatalogue = $field['i18n_file']; } return sfContext::getInstance()->getI18N()->__($field['name'], array(), $i18nCatalogue).'::'.$name; } return $name; } public static function getCurrentData() { return stPropelImporter::$current_data; } /** * Zwraca instancje loggera * * @return stImportExportLog */ public function getLogger() { return $this->logger; } /** * Fixed function fgetcsv, parameter compatible with fgetcsv * * handle - handle to the file * len - unsued * delimeter - delimeter * enclosure - enclosure */ public static function fixed_fgetcsv($handle, $len = null, $delimeter = ',', $enclosure = '"') { //read lines until even numbers of enclosures or EOF $line = fgets($handle); while (!feof($handle) && substr_count($line,'"')%2) $line .= fgets($handle); // return array $ret = array(); // cell value $cell = ""; //split line into cells foreach (explode($delimeter,$line) as $value) { //assign value to cell $cell .= $value; // if cell has even numbers of enclosure if (substr_count($cell,$enclosure)%2 == 0) { //remove delimeters from begining and end if (substr($cell,0,1) == $enclosure && substr($cell,0,2) != $enclosure.$enclosure) $cell = ltrim($cell, $enclosure); if (substr($cell,-1) == $enclosure && substr($cell,-2) != $enclosure.$enclosure) $cell = rtrim($cell, $enclosure); //replace escape enclosures $cell = str_replace($enclosure.$enclosure,$enclosure, $cell); $ret[] = $cell; $cell = ''; } // cell not endend append deliteter and continue else {$cell .= $delimeter;} } return $ret; } }