From 8970cb6836d1050fcfbedac25ef1f676e585b391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nick=20Dil=C3=9Fner?= Date: Wed, 26 Oct 2016 15:22:56 +0200 Subject: [PATCH] + (add) Importer logic --- .../Importer/Helper/Config.php | 26 ++ .../Importer/Helper/Logger.php | 132 +++++++++ .../Importer/Model/Config.php | 153 ++++++++++ .../Importer/Model/Decorator.php | 68 +++++ .../Exception/InvalidArgumentException.php | 11 + .../Importer/Model/Decorator/HtmlList.php | 115 ++++++++ .../Importer/Model/Decorator/Interface.php | 34 +++ .../Importer/Model/Factory.php | 53 ++++ .../Importer/Model/Reader.php | 225 ++++++++++++++ .../Importer/Model/Reader/Csv.php | 59 ++++ .../Exception/InvalidArgumentException.php | 11 + .../Exception/UnexpectedValueException.php | 11 + .../Importer/Model/Reader/Interface.php | 52 ++++ .../Importer/Model/Reader/Xml.php | 143 +++++++++ .../Importer/Model/Worker.php | 277 ++++++++++++++++++ .../Importer/Model/Writer.php | 227 ++++++++++++++ .../Importer/Model/Writer/Interface.php | 67 +++++ .../Importer/Model/Writer/Product.php | 84 ++++++ .../Kirchbergerknorr/Importer/etc/config.xml | 20 ++ app/etc/modules/Kirchbergerknorr_Importer.xml | 9 + 20 files changed, 1777 insertions(+) create mode 100644 app/code/community/Kirchbergerknorr/Importer/Helper/Config.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/Helper/Logger.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/Model/Config.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/Model/Decorator.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/Model/Decorator/Exception/InvalidArgumentException.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/Model/Decorator/HtmlList.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/Model/Decorator/Interface.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/Model/Factory.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/Model/Reader.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/Model/Reader/Csv.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/Model/Reader/Exception/InvalidArgumentException.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/Model/Reader/Exception/UnexpectedValueException.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/Model/Reader/Interface.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/Model/Reader/Xml.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/Model/Worker.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/Model/Writer.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/Model/Writer/Interface.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/Model/Writer/Product.php create mode 100644 app/code/community/Kirchbergerknorr/Importer/etc/config.xml create mode 100644 app/etc/modules/Kirchbergerknorr_Importer.xml diff --git a/app/code/community/Kirchbergerknorr/Importer/Helper/Config.php b/app/code/community/Kirchbergerknorr/Importer/Helper/Config.php new file mode 100644 index 0000000..57c18bb --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Helper/Config.php @@ -0,0 +1,26 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Kirchbergerknorr_Importer_Helper_Config +{ + const PATH = 'kirchbergerknorr/importer/'; + + // TODO: implement multiple config possibility + /** + * get importer config + * + * @param string $key config key + * @param int $type (NOT IMPLEMENTED YET) from which importer config + * @return mixed config value + */ + public function get($key, $type = 0) + { + return Mage::getStoreConfig(self::PATH . $key); + } +} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/Helper/Logger.php b/app/code/community/Kirchbergerknorr/Importer/Helper/Logger.php new file mode 100644 index 0000000..25da5d4 --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Helper/Logger.php @@ -0,0 +1,132 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +// TODO: validate if interface is needed +// TODO: get calling method +/** + * Class Kirchbergerknorr_PcoInterface_Helper_Logger for logging + */ +class Kirchbergerknorr_Importer_Helper_Logger +{ + // TODO: config + /** + * @var string log destination path + */ + private static $destination = 'importer/import'; + + /** + * @var bool set if debug is enabled for interface + */ + protected static $debug = null; + + /** + * @var string suffix for logging file, will be created in constructor + */ + protected $suffix = ''; + + /** + * Kirchbergerknorr_Importer_Helper_Logger constructor. + * + * @param string[] $arguments for file suffix + */ + public function __construct($arguments = array()) + { + $path = array(); + if (isset($arguments['type'])) { + $path[] = $arguments['type']; + } + if (isset($arguments['name'])) { + $path[] = $arguments['name']; + } + + $this->suffix = implode('_', $path); + } + + /** + * Log given message + * + * @param string $message message to log + * @param string $type (optional) logging type, for different file location + * + * @return Kirchbergerknorr_Importer_Helper_Logger + */ + public function log($message, $type = '') + { + // TODO: Db logging and file logging + if($message) { + // Log for admin Backend + echo $message . "\n"; + Mage::log($message . "\n", null, static::$destination . $this->getSuffix($type) . '.log', true); + } + return $this; + } + + /** + * Debug Log given message if debug is enabled in config + * + * @param string|mixed $message message to log + * @param string $type (optional) logging type, for different file location + * + * @return Kirchbergerknorr_Importer_Helper_Logger for chaining + */ + public function debug($message, $type = '') + { + if(self::isDebug() && $message) { + Mage::log(((is_string($message)) ? $message : var_export($message, true)) . "\n", null, static::$destination . $this->getSuffix($type) . '_debug.log', true); + } + return $this; + } + + // TODO: think about a own exception log + /** + * @param Exception $exception exception to log + * + * @return Kirchbergerknorr_Importer_Helper_Logger for chaining + */ + public function exception($exception) + { + Mage::logException($exception); + return $this; + } + + /** + * is debug enabled + * + * @return bool true if enabled, else false + */ + public static function isDebug() + { + if (self::$debug === null) { + // TODO: create config + self::$debug = true; +// self::$debug = (bool) Mage::getStoreConfig('kirchbergerknorr/importer/debug_enabled'); + } + return self::$debug; + } + + /** + * get file suffix + * + * @param string $type (optional) additional suffix for file + * + * @return string file suffix + */ + protected function getSuffix($type = '') + { + $suffix = array(); + if ($this->suffix) { + $suffix[] = strtolower($this->suffix); + } + if ($type) { + $suffix[] = strtolower($type); + } + return (($suffix) ? '_' . implode('_', $suffix) : ''); + } +} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/Model/Config.php b/app/code/community/Kirchbergerknorr/Importer/Model/Config.php new file mode 100644 index 0000000..a580e1e --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Model/Config.php @@ -0,0 +1,153 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Kirchbergerknorr_Importer_Model_Config +{ + const IMPORTER_NAMESPACE = 'kirchbergerknorr_importer'; + + /** + * @var mixed[] config data + */ + protected $config; + + /** + * @var Kirchbergerknorr_Importer_Model_Factory helper factory + */ + protected $factory; + + /** + * Kirchbergerknorr_Importer_Model_Config constructor. + * + * @param mixed[] $config importer config + */ + public function __construct($config) + { + $this->config = $config; + $this->factory = Mage::getModel('kirchbergerknorr_importer/factory'); + } + + // TODO: refactor if get as simple get- value is needed, else remove this + /** + * get raw values from config without magic + * + * @param string $key config key + * + * @return mixed raw value + */ + public function getValue($key) + { + if (($value = $this->value($key))) { + return $value; + } + return null; + } + + /** + * get value from config for given key - uses magic + * + * @param string $key config key + * @param mixed[] $arguments (optional) additional parameters for magic object creation + * + * @return mixed value from config + */ + public function get($key, $arguments = array()) + { + return $this->config($key, $arguments); + } + + /** + * get dynamic value from config - replaces YYYY, MM, DD with date + * + * @param string $key config key + * + * @return mixed value, replaced with date if it is a string + */ + public function dynamic($key) + { + $value = $this->get($key); + if ($value && is_string($value)) { + $date = new DateTime('now'); + // TODO: optimize - currently all occurrences are replaced + $value = str_replace( + array('YYYY', 'MM', 'DD'), + array($date->format('Y'), $date->format('m'), $date->format('d')), + $value + ); + } + return $value; + } + + /** + * magic config return values depending on type + * + * @param string $key key in config which is wanted + * @param mixed[] $arguments (optional) additional arguments for objects + * @param string[] $identifiers (optional) identifiers for array handling -> which key is needed to create a object, else it returns only a array + * + * @return mixed magic values + */ + protected function config($key, $arguments = array(), $identifiers = array('model')) + { + if ($value = $this->value($key)) { + switch (gettype($value)) { + case 'array': + // exclude mapping, to have a free namespace + if ($key !== 'mapping') { + foreach ($identifiers as $identifier) { + if (!empty($value[$identifier]) && is_string($value[$identifier])) {; + $arguments = (!empty($value['arguments'])) ? array_merge($arguments, $value['arguments']) : $arguments; + $type = end(explode('/', $key)); + return $this->factory->get($type, $value[$identifier], $arguments); + } + } + } + return $value; + break; + case 'string': + // TODO: think about it, possible that it tries to crerate to much models, but config is only read one time + if ($model = $this->factory->get($key, $value, $arguments)) { + return $model; + } + return $value; + break; + case 'object': + // TODO: implement stuff, if needed + break; + case 'NULL': + // TODO: implement, if needed + break; + default: + return $value; + break; + } + return $value; + } + return null; + } + + /** + * get config value from key - can handle sub-keys splitted with '/' + * + * @param string $key key to get + * + * @return mixed|null value if found + */ + protected function value($key) + { + $config = $this->config; + foreach (explode('/', $key) as $key) { + if (isset($config[$key])) { + $config = $config[$key]; + } else { + return null; + } + } + return $config; + } +} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/Model/Decorator.php b/app/code/community/Kirchbergerknorr/Importer/Model/Decorator.php new file mode 100644 index 0000000..4eebd13 --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Model/Decorator.php @@ -0,0 +1,68 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +abstract class Kirchbergerknorr_Importer_Model_Decorator implements Kirchbergerknorr_Importer_Model_Decorator_Interface +{ + /** + * @var mixed data to decorate + */ + protected $data; + + /** + * @var string name for writer + */ + protected $name; + + // TODO: use TRAITS + /** + * Kirchbergerknorr_Importer_Model_Decorator constructor. + * + * @param mixed[] $arguments constructor arguments + * @param string[] $keys (optional) additional keys to check in arguments + * + * @throws Kirchbergerknorr_Importer_Model_Decorator_Exception_InvalidArgumentException if empty name in arguments + * + * @typedef Arguments + * @var string name name for which decorator is used + */ + public function __construct($arguments, $keys = array()) + { + if (empty($arguments['name'])) { + throw new Kirchbergerknorr_Importer_Model_Decorator_Exception_InvalidArgumentException('Mapping Name Missing'); + } + foreach (array_merge($keys, array('name')) as $config) { + if (isset($arguments[$config])) { + $this->$config = $arguments[$config]; + } + } + } + + /** + * get name for which the decorator is for + * + * @return string name for writer + */ + public function getName() + { + return $this->name; + } + + /** + * set decorator decorate + * + * @param mixed $data data to decorate + * + * @return Kirchbergerknorr_Importer_Model_Decorator for chaining + */ + public function setData($data) + { + $this->data = $data; + return $this; + } +} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/Model/Decorator/Exception/InvalidArgumentException.php b/app/code/community/Kirchbergerknorr/Importer/Model/Decorator/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..9cf84d5 --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Model/Decorator/Exception/InvalidArgumentException.php @@ -0,0 +1,11 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Kirchbergerknorr_Importer_Model_Decorator_Exception_InvalidArgumentException extends InvalidArgumentException +{} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/Model/Decorator/HtmlList.php b/app/code/community/Kirchbergerknorr/Importer/Model/Decorator/HtmlList.php new file mode 100644 index 0000000..99b88ab --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Model/Decorator/HtmlList.php @@ -0,0 +1,115 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Kirchbergerknorr_Importer_Model_Decorator_HtmlList extends Kirchbergerknorr_Importer_Model_Decorator +{ + /** + * @var bool decorate empty list + */ + public $empty = false; + + // TODO: implement + /** + * @var string merge list - leafs will be merged to one + */ + public $merge = ''; + + /** + * Kirchbergerknorr_Importer_Model_Decorator_HtmlList constructor. + * + * @inheritdoc + * @var string merge (not implement) if set merge last data notes (leafs) to one '
  • ' and use value as separator + * @var bool empty if set empty 'li's will be created if data value is empty + * + * @param mixed[] $arguments + */ + public function __construct($arguments) + { + parent::__construct($arguments); + + // TODO: use PHPTrap for arguments extraction + foreach (array('empty', 'merge') as $key) { + if (isset($arguments[$key])) { + $this->$key = $arguments[$key]; + } + } + } + + // TODO: remove empty li values through construct arguments + /** + * decorate values + * + * @return string decorated value + */ + public function __toString() + { + // TODO: optimize - this is currently for a string[][] array + $return = ''; + if ($data = current($this->data)) { + $return .= $this->decorate($data); + } + return $return; + } + + // TODO: validate str-length + // TODO: implement merged + /** + * decorate values + * + * @param mixed $data data to be decorated + * + * @return string decorated data + */ + protected function decorate($data) + { + if($this->validate($data)) { + if (is_array($data)) { + // TODO: find a better solution for [null] + if ($this->validate(current($data))) { + $return = ''; + } + } else { + // TODO: check if useful + return ($this->validate($data)) ? $data : ''; + } + } + return ''; + } + + // TODO: count str-length + /** + * validates if data is append-able + * + * @param mixed $data data that will be set + * @param bool $recursive look recursive through given data + * + * @return bool true if valid + */ + protected function validate($data, $recursive = false) + { + if ($this->empty) { + return true; + } + if ($recursive && is_array($data)) { + foreach ($data as $value) { + if (is_array($value)) { + return $this->validate($value, true); + } elseif ($value) { + return true; + } + } + return false; + } + return (!empty($data)); + } +} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/Model/Decorator/Interface.php b/app/code/community/Kirchbergerknorr/Importer/Model/Decorator/Interface.php new file mode 100644 index 0000000..6e9c11b --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Model/Decorator/Interface.php @@ -0,0 +1,34 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +interface Kirchbergerknorr_Importer_Model_Decorator_Interface +{ + /** + * set data to decorator + * + * @param mixed $data + * + * @return Kirchbergerknorr_Importer_Model_Decorator_Interface for chaining + */ + public function setData($data); + + /** + * get name of property key which decorator represent + * + * @return string name of property key which decorator represent + */ + public function getName(); + + /** + * decorate + * + * @return string decorated data + */ + public function __toString(); +} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/Model/Factory.php b/app/code/community/Kirchbergerknorr/Importer/Model/Factory.php new file mode 100644 index 0000000..9493010 --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Model/Factory.php @@ -0,0 +1,53 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Kirchbergerknorr_Importer_Model_Factory +{ + const IMPORTER_NAMESPACE = 'kirchbergerknorr_importer/'; + + /** + * get reader model + * + * @param string $name name of specific model + * @param mixed $data model data + * + * @return Kirchbergerknorr_Importer_Model_Reader reader model + */ + public function getReader($name, $data) + { + return $this->get('reader', $name, $data); + } + + /** + * get writer model + * + * @param string $name name of specific model + * @param mixed $data model data + * + * @return Kirchbergerknorr_Importer_Model_Writer writer model + */ + public function getWriter($name, $data) + { + return $this->get('writer', $name, $data); + } + + /** + * get model + * + * @param string $type model type + * @param string $name name of specific model + * @param mixed $arguments (optional) model arguments + * + * @return false|mixed + */ + public function get($type, $name, $arguments = array()) + { + return Mage::getModel(self::IMPORTER_NAMESPACE . $type . '_' . $name, $arguments); + } +} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/Model/Reader.php b/app/code/community/Kirchbergerknorr/Importer/Model/Reader.php new file mode 100644 index 0000000..7779704 --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Model/Reader.php @@ -0,0 +1,225 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +abstract class Kirchbergerknorr_Importer_Model_Reader implements Kirchbergerknorr_Importer_Model_Reader_Interface, RecursiveIterator +{ + /** + * @var string[] content + */ + protected $_data; + + /** + * @var Kirchbergerknorr_Importer_Helper_Logger logger + */ + protected $_logger; + + /** + * @var string source - path or url + */ + protected $_source; + + /** + * @var string if set data will be copied to that path after read + */ + protected $_copy; + + // TODO: use TRAITS + /** + * Kirchbergerknorr_Importer_Model_Reader constructor. + * + * @param mixed[] $arguments (optional) constructor arguments + * @param string[] $keys (optional) additional keys which will be searched in arguments + * + * @typedef Arguments + * @var string copy path to copy data after read + * @var string source path / url where data is + */ + public function __construct($arguments = array(), $keys = array()) + { + foreach (array_merge($keys, array('copy', 'source')) as $config) { + if (isset($arguments[$config])) { + $this->{'_' . $config} = $arguments[$config]; + } + } + } + + /** + * copy given data to location + * + * @param mixed $data data to copy + * + * @return Kirchbergerknorr_Importer_Model_Reader for chaining + */ + protected function copy($data) + { + // TODO: implement like \Kirchbergerknorr_Import_Model_Config::dynamic() + if (!empty($this->_copy)) { + $date = new DateTime('now'); + $path = Mage::getBaseDir() . DS . ltrim($this->_copy, DS) . 'imported_' . $date->format('Ymd') . '.log'; + file_put_contents( + $path, + $data + ); + + } + return $this; + } + + // current(); + return ((isset($current[$key])) ? $current[$key] : null); + } + + /** + * set data + * + * @param mixed $data data to set + * + * @return Kirchbergerknorr_Importer_Model_Reader + */ + public function setData(&$data) + { + $this->_data = $data; + $this->getLogger()->debug(array('setData', $data)); + return $this; + } + + /** + * reads data from source + * + * @return Kirchbergerknorr_Importer_Model_Reader for chaining + * + * @throws Kirchbergerknorr_Importer_Model_Reader_Exception_InvalidArgumentException if no source is set + * @throws Kirchbergerknorr_Importer_Model_Reader_Exception_UnexpectedValueException if data is not read-able + */ + public function read() + { + if (!$this->_source) { + throw new Kirchbergerknorr_Importer_Model_Reader_Exception_InvalidArgumentException('source is missing'); + } + if (($data = file_get_contents($this->_source)) && $data !== false) { + $this->getLogger()->log('read data - success'); + return $this + ->copy($data) + ->setData($data); + } + throw new Kirchbergerknorr_Importer_Model_Reader_Exception_UnexpectedValueException('while reading source'); + } + // INTERFACE> + + // _data); + } + + /** + * Moves the current position to the next element. + * + * @return void + */ + public function next() + { + next($this->_data); + } + + /** + * Returns the key of the current element. + * + * @return string|int key of current + */ + public function key() + { + return key($this->_data); + } + + /** + * This method is called after Iterator::rewind() and Iterator::next() to check if the current position is valid. + * + * @return bool true if valid + */ + public function valid() + { + return (key_exists($this->key(), $this->_data)); + } + + /** + * Rewinds back to the first element of the Iterator. + * + * @return void + */ + public function rewind() + { + reset($this->_data); + } + // ITERATOR> + + // current())); + } + + /** + * Returns an iterator for the current entry. + * + * @return Kirchbergerknorr_Importer_Model_Reader iterator of current position + */ + public function getChildren() + { + // late static binding + $class = get_called_class(); + /** @var Kirchbergerknorr_Importer_Model_Reader $class */ + $class = new $class(); + return $class->setData($this->current()); + } + // RECURSIVE-ITERATOR> + + // _logger = $logger; + } + + /** + * get logger + * + * @return Kirchbergerknorr_Importer_Helper_Logger logger + */ + public function getLogger() + { + return $this->_logger; + } + // LOGGER> +} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/Model/Reader/Csv.php b/app/code/community/Kirchbergerknorr/Importer/Model/Reader/Csv.php new file mode 100644 index 0000000..e805957 --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Model/Reader/Csv.php @@ -0,0 +1,59 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Kirchbergerknorr_Importer_Model_Reader_Csv extends Kirchbergerknorr_Importer_Model_Reader +{ + /** + * @var string csv column delimiter + */ + protected $_delimiter = ","; + + /** + * @var string csv field enclosure -> frame + */ + protected $_enclosure = '"'; + + /** + * @var string csv escaping char + */ + protected $_escape = "\\"; + + /** + * @inheritdoc + * @var string delimiter csv column delimiter + * @var string enclosure csv field enclosure -> frame + * @var string escape csv escaping char + */ + public function __construct($arguments = array()) + { + parent::__construct($arguments, array('delimiter', 'enclosure', 'escape')); + } + + /** + * @inheritdoc + */ + public function read() + { + if (!$this->_source) { + throw new Kirchbergerknorr_Importer_Model_Reader_Exception_InvalidArgumentException('source is missing'); + } + ini_set('auto_detect_line_endings', TRUE); + // TODO: create a copy for external csv's -> also this way is not optimized, maybe override iterator methods + $data = array(); + if ($handle = fopen($this->_source, 'r')) { + while ($row = fgetcsv($handle, 0, $this->_delimiter, $this->_enclosure, $this->_escape)) { + $data[] = $row; + } + // TODO: copy + return $this + ->setData($data); + } + throw new Kirchbergerknorr_Importer_Model_Reader_Exception_UnexpectedValueException('while reading source'); + } +} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/Model/Reader/Exception/InvalidArgumentException.php b/app/code/community/Kirchbergerknorr/Importer/Model/Reader/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..9a77293 --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Model/Reader/Exception/InvalidArgumentException.php @@ -0,0 +1,11 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Kirchbergerknorr_Importer_Model_Reader_Exception_InvalidArgumentException extends InvalidArgumentException +{} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/Model/Reader/Exception/UnexpectedValueException.php b/app/code/community/Kirchbergerknorr/Importer/Model/Reader/Exception/UnexpectedValueException.php new file mode 100644 index 0000000..9f19640 --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Model/Reader/Exception/UnexpectedValueException.php @@ -0,0 +1,11 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Kirchbergerknorr_Importer_Model_Reader_Exception_UnexpectedValueException extends UnexpectedValueException +{} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/Model/Reader/Interface.php b/app/code/community/Kirchbergerknorr/Importer/Model/Reader/Interface.php new file mode 100644 index 0000000..dd7ba5d --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Model/Reader/Interface.php @@ -0,0 +1,52 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +interface Kirchbergerknorr_Importer_Model_Reader_Interface +{ + /** + * get value from data for given key + * + * @param string $key key to get from data + * + * @return mixed value + */ + public function __get($key); + + /** + * set data + * + * @param mixed $data data to set + * + * @return Kirchbergerknorr_Importer_Model_Reader_Interface + */ + public function setData(&$data); + + /** + * reads data from source + * + * @return Kirchbergerknorr_Importer_Model_Reader_Interface for chaining + */ + public function read(); + + /** + * set logger + * + * @param Kirchbergerknorr_Importer_Helper_Logger $logger set logger + * + * @return Kirchbergerknorr_Importer_Model_Reader_Interface for chaining + */ + public function setLogger($logger); + + /** + * get logger + * + * @return Kirchbergerknorr_Importer_Helper_Logger logger + */ + public function getLogger(); +} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/Model/Reader/Xml.php b/app/code/community/Kirchbergerknorr/Importer/Model/Reader/Xml.php new file mode 100644 index 0000000..1da2a48 --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Model/Reader/Xml.php @@ -0,0 +1,143 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Kirchbergerknorr_Importer_Model_Reader_Xml extends Kirchbergerknorr_Importer_Model_Reader +{ + /** + * @var SimpleXMLIterator content + */ + protected $_data; + + /** + * extract content recursive from data + * + * @param SimpleXMLIterator $data iterator with content + * + * @return mixed[]|null + */ + private function extract(SimpleXMLIterator $data) + { + if ($data->count() > 0) { + if ($data->hasChildren()) { + // extract recursive + $return = array(); + foreach ($data->getChildren() as $key => $child) { + // reset pointer, to make multiple usage possible + $child->rewind(); + // no key possible, because + $return[$key][] = $this->extract($child); + } + return $return; + } else { + if ($data->count() == 1) { + return $data->__toString(); + } else { + // extract array data + $return = array(); + foreach ($data as $key => $unusable) { + $return[$key] = $this->extract($data->$key); + } + return $return; + } + } + } + return null; + } + + // recursive + /** + * @inheritdoc + */ + public function __get($key) + { + /** @var SimpleXMLIterator $data */ + $data = $this->_data->current()->$key; + // prevent return an Iterator +// return ($data->count() == 1) ? $data->__toString() : null; + // reset pointer, to make multiple usage possible + $data->rewind(); + return $this->extract($data); + } + + /** + * @inheritdoc + * + * @return Kirchbergerknorr_Importer_Model_Reader_Xml for chaining + */ + public function setData(&$data) + { + return parent::setData(($data instanceof SimpleXMLIterator) ? $data : new SimpleXMLIterator($data)); + } + // INTERFACE> + + // TODO: check if a good solution or conversion is needed + // _data->current()); + } + + /** + * @inheritdoc + */ + public function next() + { + $this->_data->next(); + } + + /** + * @inheritdoc + */ + public function key() + { + return $this->_data->key(); + } + + /** + * @inheritdoc + */ + public function valid() + { + return $this->_data->valid(); + } + + /** + * @inheritdoc + */ + public function rewind() + { + $this->_data->rewind(); + } + // ITERATOR> + + // _data->hasChildren(); + } + + /** + * @inheritdoc + * + * @return Kirchbergerknorr_Importer_Model_Reader + */ + public function getChildren() + { + $class = new self(); + return $class->setData($this->_data->getChildren()); + } + // RECURSIVE-ITERATOR> +} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/Model/Worker.php b/app/code/community/Kirchbergerknorr/Importer/Model/Worker.php new file mode 100644 index 0000000..c29a1b3 --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Model/Worker.php @@ -0,0 +1,277 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Kirchbergerknorr_Importer_Model_Worker +{ + /** + * @var Kirchbergerknorr_Importer_Model_Config worker config + */ + protected $config; + + /** + * @var Kirchbergerknorr_Importer_Model_Reader reader + */ + protected $reader; + + /** + * @var Kirchbergerknorr_Importer_Model_Writer writer + */ + protected $writer; + + /** + * @var mixed[] reader / writer mapping + */ + protected $mapping; + + /** + * @var mixed[] filters for writer + */ + protected $filters; + + /** + * @var Kirchbergerknorr_Importer_Helper_Logger logger + */ + protected $logger; + + /** + * @var bool if set writer overrides fields with empty value + */ + protected $override = false; + + /** + * @var Kirchbergerknorr_Importer_Model_Decorator[] decorator cache for decorators which are configured as array + */ + protected $decorators = array(); + + /** + * Kirchbergerknorr_Importer_Model_Worker constructor. + * + * @param mixed[] $config + * + * @typedef Config + * @var {string} name importer name for loging + * @var {Writer} writer writer config + * @var {Reader} reader reader config + * @var {bool} override if set override with empty values if reader value is empty + * @var {string[]} filters filter mapping + * @var {mixed[]|Decorator[]|Callback[]} mapping mapping as key = reader, value = writer + * + * @typedef {array} Writer + * @var {bool} update set to update filtered model + * @var {bool} create set to create new models if no one was found with filter + * @var {bool} delete (not implemented yet) set to delete filtered model + * @var {mixed[]} defaults defaults which will always set to model + * + * @typedef {array} Writer.Product + * @var {string[]} defaultStock default values for stock item after save + * + * @typedef {array} Reader + * @var {string} copy path to copy data after read + * @var {string} source path / url where data is + * + * @typedef {array} Reader.Csv + * @var {string} delimiter csv column delimiter + * @var {string} enclosure csv field enclosure -> frame + * @var {string} escape csv escaping char + * + * @typedef {array} Decorator + * @var {string} name name for which decorator is used + * + * @typedef {array} Decorator.HtmlList + * @var {string} delimiter csv column delimiter + * @var {string} enclosure csv field enclosure -> frame + * @var {string} escape csv escaping char + * + * @typedef {function} Callback + * @var {mixed} value value from reader + * @var {WriterObject} writer writer + * @var {ReaderObject} reader reader + */ + public function __construct($config) + { + $this->config = Mage::getModel('kirchbergerknorr_importer/config', $config); + + // required + $this->init(array('reader', 'writer', 'mapping', 'filters'), true); + // TODO: think about config position ... override is a writer option, but worker needs it to prevent useless code-runs + // optional + $this->init(array('override')); + + // inject logger + $name = $this->config->get('name'); + foreach (array('reader', 'writer') as $worker) { + if(!$this->$worker->getLogger()) { + $this->$worker->setLogger(Mage::helper('kirchbergerknorr_importer/logger', array( + 'name' => $name, + 'type' => $worker, + ))); + } + } + + // create own logger + $this->setLogger(Mage::helper('kirchbergerknorr_importer/logger', array( + 'name' => $name, + 'type' => 'worker' + ))); + } + + /** + * initialize worker from config for given keys + * + * @param string[] $keys + * @param bool $required + * @return void + * + * @throws \InvalidArgumentException if required key is missing + */ + protected function init($keys, $required = false) + { + // TODO: think about own WorkerExceptions + foreach ($keys as $key) { + $value = $this->config->dynamic($key); + if ($value !== null) { + $this->$key = $value; + } elseif ($required) { + throw new InvalidArgumentException("$key is missing"); + } + } + } + + // TODO: validate if filter model is better then this + // TODO: validate if this is enough, that filter is only first lvl or if recursion is needed + /** + * set filter to writer with values from reader + * + * @param string[] $filters configured filters + * @param Kirchbergerknorr_Importer_Model_Reader_Interface $reader reader + * @param Kirchbergerknorr_Importer_Model_Writer_Interface $writer writer + * @return Kirchbergerknorr_Importer_Model_Worker for chaining + */ + protected function setFilters($filters, Kirchbergerknorr_Importer_Model_Reader_Interface $reader, Kirchbergerknorr_Importer_Model_Writer_Interface $writer) + { + foreach ($filters as $read => $write) { + $writer->addFilter($write, $reader->$read); + } + $writer->setDefaults(); + return $this; + } + + /** + * do the job + * + * @throws Kirchbergerknorr_Importer_Model_Reader_Exception_UnexpectedValueException if source cause an exception + */ + public function run() + { + try { + $this->getLogger()->log('START'); + $this->reader->read(); + + // same as do-while-loop, but without possible errors + // Explained: for (startup(); while(); beforeNextIteration()) { ... } + for ($this->reader->rewind(); $this->reader->valid(); $this->reader->next()) { + + $writer = clone $this->writer; + $this->setFilters($this->filters, $this->reader, $writer); + + // run only if model was found or create is active + if ($writer->isWriteAble()) { + $this->read($this->mapping, $this->reader, $writer); + + $writer->save(); + } + } + $this->getLogger()->log('END'); + } catch (Exception $exception) { + $this->getLogger()->exception($exception); + exit; + } + } + + /** + * read mapping for current interval + * + * @param mixed[] $mapping configured field mapping + * @param Kirchbergerknorr_Importer_Model_Reader $reader source wrapper + * @param Kirchbergerknorr_Importer_Model_Writer $writer magento model wrapper + * @param string $key (optional) combined config key to get sub-config in recursion + * @return void + */ + public function read($mapping, $reader, $writer, $key = 'mapping') + { + foreach ($mapping as $read => $write) { + $value = null; + if (($value = $reader->$read) || $this->override) { + switch (gettype($write)) { + case 'string': + $writer->$write = $value; + break; + case 'object': + if ($write instanceof Kirchbergerknorr_Importer_Model_Decorator_Interface) { + $writer->{$write->getName()} = $write->setData($value)->__toString(); + } + if (is_callable($write)) { + // Callback must handle data itself + /** + * @typedef Callback + * @var mixed value value value from source + * @var Kirchbergerknorr_Importer_Model_Writer writer $writer writer to set data + * @var Kirchbergerknorr_Importer_Model_Reader reader $reader reader for additional things to do + */ + $write($reader->$read, $writer, $reader); + } + break; + case 'array': + $key = "$key/$read"; + if (isset($write['decorator'])) { + // if magic config get a model it was a decorator + $decorator = (isset($this->decorators[$key])) ? $this->decorators[$key] : $this->decorators[$key] = $this->config->get("$key/decorator"); + if ($decorator && $decorator instanceof Kirchbergerknorr_Importer_Model_Decorator_Interface) { + $writer->{$decorator->getName()} = $decorator->setData($value)->__toString(); + } + } else if ($reader->hasChildren()) { + // recursion + $this->read($write, $reader->getChildren(), $writer, $key); + } + break; + default: + // TODO: validate + break; + } + } + $this->getLogger() + ->log("read - key: '$key' type: '" . gettype($write) . "' value: " . $value) + ->debug(array('read', gettype($write), $value)); + } + } + + // logger = $logger; + return $this; + } + + /** + * get logger + * + * @return Kirchbergerknorr_Importer_Helper_Logger logger + */ + public function getLogger() + { + return $this->logger; + } + // LOGGER> +} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/Model/Writer.php b/app/code/community/Kirchbergerknorr/Importer/Model/Writer.php new file mode 100644 index 0000000..d515ce1 --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Model/Writer.php @@ -0,0 +1,227 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +abstract class Kirchbergerknorr_Importer_Model_Writer implements Kirchbergerknorr_Importer_Model_Writer_Interface +{ + /** + * @var Mage_Catalog_Model_Abstract dummy model to get collection from + */ + protected $_model; + + /** + * @var Kirchbergerknorr_Importer_Helper_Logger logger + */ + protected $_logger; + + /** + * @var Mage_Eav_Model_Entity_Collection_Abstract collection for filter + */ + protected $_collection; + + /** + * @var Mage_Catalog_Model_Abstract filtered model + */ + protected $_filtered; + + /** + * @var mixed[] default values for model + */ + protected $_defaults = array(); + + /** + * @var bool if true update filtered model + */ + protected $_update = true; + + /** + * @var bool if true create model if no filtered was found + */ + protected $_create = true; + + // TODO: implement + /** + * @var bool if set delete filtered model + */ + protected $_delete = false; + + // TODO: use TRAITS + /** + * Kirchbergerknorr_Importer_Model_Writer constructor. + * + * @param array $arguments + * @param array $keys + * + * @typedef Arguments + * @var bool update set to update filtered model + * @var bool create set to create new models if no one was found with filter + * @var bool delete (not implemented yet) set to delete filtered model + * @var mixed[] defaults defaults which will always set to model + */ + public function __construct($arguments = array(), $keys = array()) + { + foreach (array_merge($keys, array('update', 'create', 'delete', 'defaults')) as $config) { + if (isset($arguments[$config])) { + $this->{'_' . $config} = $arguments[$config]; + } + } + $this->init(); + } + + /** + * init model + * + * @return Kirchbergerknorr_Importer_Model_Writer for chaining + */ + protected function init() + { + // reset filtered + $this->_filtered = null; + + $this->_collection = clone $this->_model->getCollection(); + return $this; + } + + /** + * add filter to model + * + * @param string $filter filter attribute name + * @param mixed $value value for filter + * @return Kirchbergerknorr_Importer_Model_Writer for chaining + */ + public function addFilter($filter, $value) + { + $this->getLogger()->log("addFilter - filter: '$filter' value: '$value'"); + $this->_collection->addAttributeToFilter($filter, $value); + return $this; + } + + // TODO: implement log create/update/... + /** + * save set data to model + * + * @return Kirchbergerknorr_Importer_Model_Writer + */ + public function save() + { + if ($this->isWriteAble()) { + $model = $this->getFiltered(); + $model->save(); + + $this->getLogger() + ->log('save model ID: ' . $model->getId()) + ->debug(array('save', $model)); + + $this->afterSave(); + } + } + + /** + * magic clone - should be called if new / next model is needed + * + * @return void + */ + public function __clone() + { + $this->init(); + } + + /** + * set given value to model for given name + * + * @param string $name key + * @param mixed $value value to set + */ + public function __set($name, $value) + { + $this->getLogger()->debug(array('__set', $name, $value)); + $this->getFiltered()->setData($name, $value); + } + + /** + * validates if model is write-able or not + * + * @return bool true if write-able + */ + public function isWriteAble() + { + $hasId = (!!$this->getFiltered()->getId()); + return ( + (!$hasId && $this->_create) + || ($hasId && $this->_update) + ); + } + + /** + * get filtered model + * + * @return Mage_Core_Model_Abstract filtered model -> needs to be validated if id is set + */ + public function getFiltered() + { + if ($this->_filtered === null) { + $this->_filtered = $this->_collection->getFirstItem(); + + if ($this->_filtered->getId()) { + $this->getLogger()->log('getFiltered - found match ID: ' . $this->_filtered->getId()); + } else { + $this->getLogger()->log('getFiltered - no match'); + } + } + return $this->_filtered; + } + + /** + * set defaults to model + * + * @return Kirchbergerknorr_Importer_Model_Writer for chaining + */ + public function setDefaults() + { + foreach ($this->_defaults as $key => $value) { + // set through magic setter + $this->$key = $value; + } + return $this; + } + + /** + * do stuff after save model + * + * @return Kirchbergerknorr_Importer_Model_Writer for chaining + */ + protected function afterSave() + { + // DUMMY - do some stuff after save in Model + return $this; + } + + // _logger = $logger; + return $this; + } + + /** + * get logger + * + * @return Kirchbergerknorr_Importer_Helper_Logger logger + */ + public function getLogger() + { + return $this->_logger; + } + // LOGGER> +} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/Model/Writer/Interface.php b/app/code/community/Kirchbergerknorr/Importer/Model/Writer/Interface.php new file mode 100644 index 0000000..dbc79e7 --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Model/Writer/Interface.php @@ -0,0 +1,67 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +interface Kirchbergerknorr_Importer_Model_Writer_Interface +{ + /** + * add filter to model + * + * @param string $filter filter attribute name + * @param mixed $value value for filter + * @return Kirchbergerknorr_Importer_Model_Writer_Interface for chaining + */ + public function addFilter($filter, $value); + + /** + * save data to model + * + * @return Kirchbergerknorr_Importer_Model_Writer_Interface for chaining + */ + public function save(); + + /** + * magic clone - should be called if new / next model is needed + * + * @return void + */ + public function __clone(); + + /** + * magic setter + * + * @param string $name key + * @param mixed $value value to set + * + * @return void + */ + public function __set($name, $value); + + /** + * set defaults to model + * + * @return Kirchbergerknorr_Importer_Model_Writer_Interface for chaining + */ + public function setDefaults(); + + /** + * set logger + * + * @param Kirchbergerknorr_Importer_Helper_Logger $logger logger + * + * @return Kirchbergerknorr_Importer_Model_Writer_Interface for chaining + */ + public function setLogger($logger); + + /** + * get logger + * + * @return Kirchbergerknorr_Importer_Helper_Logger logger + */ + public function getLogger(); +} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/Model/Writer/Product.php b/app/code/community/Kirchbergerknorr/Importer/Model/Writer/Product.php new file mode 100644 index 0000000..a687031 --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/Model/Writer/Product.php @@ -0,0 +1,84 @@ + + * @copyright Copyright (c) 2016 kirchbergerknorr GmbH (http://www.kirchbergerknorr.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Kirchbergerknorr_Importer_Model_Writer_Product extends Kirchbergerknorr_Importer_Model_Writer +{ + /** + * @var int[] stock values from worker + */ + protected $_stock = array(); + + // TODO: validate defaults + /** + * @var int[] default stock values will be merged with stock data + */ + protected $_defaultsStock = array( + 'is_in_stock' => 0, +// 'stock_id' => 1, + 'manage_stock' => 0, + 'notify_stock_qty' => 0, + 'use_config_notify_stock_qty' => 0, + 'use_config_qty_increments' => 0, + 'use_config_enable_qty_inc' => 0, + 'use_config_min_qty' => 0, + 'use_config_backorders' => 0, + 'use_config_manage_stock' => 0, + 'min_sale_qty' => 0, + 'use_config_min_sale_qty' => 0, + 'max_sale_qty' => 0, + 'use_config_max_sale_qty' => 0, + 'qty' => 0, + ); + + /** + * @inheritdoc + * @var string[] defaultStock default values for stock item after save + */ + public function __construct($arguments = array()) + { + $this->_model = Mage::getModel('catalog/product'); + parent::__construct($arguments, array('defaultsStock')); + } + + /** + * @inheritdoc + */ + public function __clone() + { + parent::__clone(); + // reset stock + $this->_stock = array(); + } + + /** + * set data to model for given name + * + * @param string $name key + * @param mixed $value value to set + */ + public function __set($name, $value) + { + if (isset($this->_defaultsStock[$name])) { + $this->_stock[$name] = $value; + } else { + parent::__set($name, $value); + } + } + + /** + * @inheritdoc + */ + protected function afterSave() + { + Mage::getModel('cataloginventory/stock_item') + ->assignProduct($this->getFiltered()) + ->addData(array_merge($this->_defaultsStock, $this->_stock)) + ->save(); + } +} \ No newline at end of file diff --git a/app/code/community/Kirchbergerknorr/Importer/etc/config.xml b/app/code/community/Kirchbergerknorr/Importer/etc/config.xml new file mode 100644 index 0000000..647c6e4 --- /dev/null +++ b/app/code/community/Kirchbergerknorr/Importer/etc/config.xml @@ -0,0 +1,20 @@ + + + + + 0.0.1 + + + + + + Kirchbergerknorr_Importer_Model + + + + + Kirchbergerknorr_Importer_Helper + + + + \ No newline at end of file diff --git a/app/etc/modules/Kirchbergerknorr_Importer.xml b/app/etc/modules/Kirchbergerknorr_Importer.xml new file mode 100644 index 0000000..19dba81 --- /dev/null +++ b/app/etc/modules/Kirchbergerknorr_Importer.xml @@ -0,0 +1,9 @@ + + + + + true + community + + + \ No newline at end of file