PageRenderTime 41ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/application/model/datasync/DataImport.php

http://github.com/integry/livecart
PHP | 543 lines | 449 code | 93 blank | 1 comment | 65 complexity | 23afc5658649b72215b2258512d057a3 MD5 | raw file
Possible License(s): LGPL-3.0
  1. <?php
  2. ClassLoader::import('application.controller.backend.abstract.ActiveGridController');
  3. abstract class DataImport
  4. {
  5. protected $application;
  6. protected $fields;
  7. protected $options;
  8. protected $uid;
  9. protected $className;
  10. protected $callback;
  11. private $lastImportName;
  12. private $flushMessage;
  13. public final function __construct(LiveCart $application)
  14. {
  15. $this->application = $application;
  16. $this->uid = uniqid('csv_');
  17. }
  18. abstract public function getFields();
  19. abstract protected function getInstance($record, CsvImportProfile $profile);
  20. public function importInstance($record, CsvImportProfile $profile, $instance = null)
  21. {
  22. try
  23. {
  24. $instance = is_null($instance) ? $this->getInstance($record, $profile) : $instance;
  25. }
  26. catch (ARNotFoundException $e)
  27. {
  28. $instance = null;
  29. }
  30. if (is_null($instance))
  31. {
  32. return;
  33. }
  34. $this->className = get_class($instance);
  35. $defLang = $this->application->getDefaultLanguageCode();
  36. if ($instance->getID())
  37. {
  38. $this->registerImportedID($instance->getID());
  39. }
  40. if ((!$instance->isExistingRecord() && ('update' == $this->options['action']))
  41. || ($instance->isExistingRecord() && ('add' == $this->options['action'])))
  42. {
  43. return false;
  44. }
  45. foreach ($profile->getFields() as $csvIndex => $field)
  46. {
  47. $column = $field['name'];
  48. $params = $field['params'];
  49. $lang = null;
  50. if (isset($params['language']))
  51. {
  52. $lang = $params['language'];
  53. }
  54. if (!isset($record[$csvIndex]) || empty($column))
  55. {
  56. continue;
  57. }
  58. $value = $record[$csvIndex];
  59. list($className, $field) = explode('.', $column, 2);
  60. if (method_exists($this, 'set_' . $className . '_' . $field))
  61. {
  62. $method = 'set_' . $className . '_' . $field;
  63. $this->$method($instance, $value, $record, $profile);
  64. }
  65. else if (method_exists($this, 'set_' . $field))
  66. {
  67. $method = 'set_' . $field;
  68. $this->$method($instance, $value, $record, $profile);
  69. }
  70. else if (isset($instance->$field) && ($instance->$field instanceof ARValueMapper) && ($className == $this->getClassName($className, $this->className)))
  71. {
  72. if (!$lang)
  73. {
  74. $instance->$field->set($value);
  75. }
  76. else
  77. {
  78. $instance->setValueByLang($field, $lang, $value);
  79. }
  80. }
  81. }
  82. $idBeforeSave = $instance->getID();
  83. $instance->save();
  84. $this->importAttributes($instance, $record, $profile->getSortedFields());
  85. foreach ($this->getReferencedData() as $section)
  86. {
  87. $method = 'import_' . $section;
  88. if (method_exists($this, $method))
  89. {
  90. $subProfile = $profile->extractSection($section);
  91. if ($subProfile->getFields())
  92. {
  93. $this->$method($instance, $record, $subProfile);
  94. }
  95. }
  96. }
  97. $this->afterSave($instance, $record);
  98. $id = $instance->getID();
  99. if ($this->callback)
  100. {
  101. call_user_func($this->callback, $instance);
  102. }
  103. $instance->__destruct();
  104. $instance->destruct(true);
  105. ActiveRecord::clearPool();
  106. return $id;
  107. }
  108. protected function getImporterInstance($type)
  109. {
  110. $class = $type . 'Import';
  111. $this->application->loadPluginClass('application.model.datasync.import', $class);
  112. return new $class($this->application);
  113. }
  114. public function importRelatedRecord($type, ActiveRecordModel $instance, $record, CsvImportProfile $profile)
  115. {
  116. return $this->getImporterInstance($type)->importInstance($record, $profile, $instance);
  117. }
  118. public function disableRecords(ARSelectFilter $filter)
  119. {
  120. if ($disable = $this->getDisableFieldHandle())
  121. {
  122. $update = new ARUpdateFilter($filter->getCondition());
  123. $update->addModifier($disable->toString(), 0);
  124. ActiveRecordModel::updateRecordSet($this->className, $update, $this->getReferencedData());
  125. }
  126. }
  127. public function deleteRecords(ARSelectFilter $filter)
  128. {
  129. $del = new ARDeleteFilter($filter->getCondition());
  130. ActiveRecordModel::deleteRecordSet($this->className, $del, $this->getReferencedData());
  131. }
  132. public function isRootCategory()
  133. {
  134. return false;
  135. }
  136. public function getLastImportedRecordName()
  137. {
  138. return $this->lastImportName;
  139. }
  140. public function setLastImportedRecordName($name)
  141. {
  142. $this->lastImportName = $name;
  143. }
  144. public function getUID()
  145. {
  146. return $this->uid;
  147. }
  148. public function setUID($uid)
  149. {
  150. $this->uid = $uid;
  151. }
  152. public function beforeImport()
  153. {
  154. }
  155. public function afterImport()
  156. {
  157. }
  158. protected function afterSave(ActiveRecordModel $instance, $record)
  159. {
  160. }
  161. protected function getDisableFieldHandle()
  162. {
  163. }
  164. protected function getReferencedData()
  165. {
  166. return array();
  167. }
  168. public function importFile(Iterator $file, CsvImportProfile $profile)
  169. {
  170. $total = 0;
  171. do
  172. {
  173. $cnt = $this->importFileChunk($file, $profile, 5);
  174. $total += $cnt;
  175. }
  176. while (!is_null($cnt));
  177. return $total;
  178. }
  179. public function importFileChunk(Iterator $file, CsvImportProfile $profile, $count)
  180. {
  181. $processed = null;
  182. while ($file->valid())
  183. {
  184. $record = $file->current();
  185. if (!is_array($record))
  186. {
  187. $file->next();
  188. continue;
  189. }
  190. foreach ($record as &$cell)
  191. {
  192. $cell = trim($cell);
  193. if (!$this->isValidUTF8($cell) && function_exists('utf8_encode'))
  194. {
  195. $cell = utf8_encode($cell);
  196. }
  197. }
  198. $status = $this->importInstance($record, $profile);
  199. $file->next();
  200. if ($this->flushMessage)
  201. {
  202. echo $this->flushMessage;
  203. flush();
  204. }
  205. if (++$processed >= $count)
  206. {
  207. break;
  208. }
  209. }
  210. return $processed;
  211. }
  212. public function setImportPosition(CsvFile $file, $position)
  213. {
  214. $processed = 0;
  215. foreach ($file as $record)
  216. {
  217. if (!is_array($record))
  218. {
  219. continue;
  220. }
  221. if (++$processed < $position)
  222. {
  223. continue;
  224. }
  225. return;
  226. }
  227. }
  228. public function setOptions($options)
  229. {
  230. $this->options = $options;
  231. }
  232. public function registerImportedID($id)
  233. {
  234. file_put_contents($this->getImportIDFileName(), $id . "\n", FILE_APPEND);
  235. }
  236. public function getImportedIDs()
  237. {
  238. $file = $this->getImportIDFileName();
  239. $res = file_exists($file) ? array_filter(explode("\n", file_get_contents($file))) : array();
  240. return $res;
  241. }
  242. public function getImportIDFileName()
  243. {
  244. $path = ClassLoader::getRealPath('cache.csvImport.') . $this->uid;
  245. if (!file_exists(dirname($path)))
  246. {
  247. mkdir(dirname($path));
  248. }
  249. return $path;
  250. }
  251. public function deletedImportIDFile()
  252. {
  253. $file = $this->getImportIDFileName();
  254. if (file_exists($file))
  255. {
  256. unlink($file);
  257. }
  258. }
  259. public function getMissingRecordFilter(CsvImportProfile $profile)
  260. {
  261. return new ARSelectFilter(new NotInCond(f($this->className . '.ID'), $this->getImportedIDs()));
  262. }
  263. protected function importAttributes(ActiveRecordModel $instance, $record, $fields, $attrIdentifier = 'eavField')
  264. {
  265. if (isset($fields[$attrIdentifier]))
  266. {
  267. $impReq = new Request();
  268. $fieldClass = ucfirst($attrIdentifier);
  269. $valueClass = 'eavField' == $attrIdentifier ? 'EavValue' : $fieldClass . 'Value';
  270. foreach ($fields[$attrIdentifier] as $specFieldID => $csvIndex)
  271. {
  272. if (empty($record[$csvIndex]))
  273. {
  274. continue;
  275. }
  276. $attr = ActiveRecordModel::getInstanceByID($fieldClass, $specFieldID, ActiveRecord::LOAD_DATA);
  277. if ($attr->isSimpleNumbers())
  278. {
  279. $impReq->set($attr->getFormFieldName(), (float)$record[$csvIndex]);
  280. }
  281. else if ($attr->isSelector())
  282. {
  283. if ($attr->isMultiValue->get())
  284. {
  285. $values = explode(',', $record[$csvIndex]);
  286. }
  287. else
  288. {
  289. $values = array($record[$csvIndex]);
  290. }
  291. foreach ($values as $fieldValue)
  292. {
  293. $fieldValue = trim($fieldValue);
  294. $f = new ARSelectFilter(
  295. new LikeCond(
  296. MultilingualObject::getLangSearchHandle(
  297. new ARFieldHandle($valueClass, 'value'),
  298. $this->application->getDefaultLanguageCode()
  299. ),
  300. $fieldValue . '%'
  301. )
  302. );
  303. $f->setLimit(1);
  304. if (!($value = $attr->getRelatedRecordSet($valueClass, $f)->shift()))
  305. {
  306. $value = call_user_func_array(array($valueClass, 'getNewInstance'), array($attr));
  307. if ($attr->type->get() == EavFieldCommon::TYPE_NUMBERS_SELECTOR)
  308. {
  309. $value->value->set($fieldValue);
  310. }
  311. else
  312. {
  313. $value->setValueByLang('value', $this->application->getDefaultLanguageCode(), $fieldValue);
  314. }
  315. $value->save();
  316. }
  317. if (!$attr->isMultiValue->get())
  318. {
  319. $impReq->set($attr->getFormFieldName(), $value->getID());
  320. }
  321. else
  322. {
  323. $impReq->set($value->getFormFieldName(), true);
  324. }
  325. }
  326. }
  327. else
  328. {
  329. $impReq->set($attr->getFormFieldName(), $record[$csvIndex]);
  330. }
  331. }
  332. $instance->loadRequestData($impReq);
  333. $instance->save();
  334. }
  335. }
  336. public function getGroupedFields($fields)
  337. {
  338. $groupedFields = array();
  339. foreach ($fields as $field => $fieldName)
  340. {
  341. if (strpos($field, '.'))
  342. {
  343. list($class, $field) = explode('.', $field, 2);
  344. $groupedFields[$class][$class . '.' . $field] = $fieldName;
  345. }
  346. }
  347. return $groupedFields;
  348. }
  349. public function skipHeader(CsvFile $file)
  350. {
  351. $record = $file->current();
  352. $file->next();
  353. }
  354. public function isCompleted(CsvFile $file)
  355. {
  356. return !$file->valid();
  357. }
  358. public function getClassName()
  359. {
  360. preg_match('/(.*)Import/', get_class($this), $match);
  361. return array_pop($match);
  362. }
  363. public function getColumnValue($record, CsvImportProfile $profile, $fieldName)
  364. {
  365. if ($profile->isColumnSet($fieldName))
  366. {
  367. return $record[$profile->getColumnIndex($fieldName)];
  368. }
  369. }
  370. public function setFlushMessage($msg)
  371. {
  372. $this->flushMessage = $msg;
  373. }
  374. public function setCallback($function)
  375. {
  376. $this->callback = $function;
  377. }
  378. protected function translate($key)
  379. {
  380. return $this->application->translate($key);
  381. }
  382. protected function makeText($key, $params)
  383. {
  384. return $this->application->makeText($key, $params);
  385. }
  386. protected function loadLanguageFile($file)
  387. {
  388. $this->application->loadLanguageFile($file);
  389. }
  390. protected function evalBool($value)
  391. {
  392. return !(!$value || in_array(strtolower($value), array('no', 'n', 'false', '0')));
  393. }
  394. protected function isValidUTF8($str)
  395. {
  396. // values of -1 represent disalloweded values for the first bytes in current UTF-8
  397. static $trailing_bytes = array (
  398. 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  399. 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  400. 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  401. 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  402. -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
  403. -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
  404. -1,-1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  405. 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
  406. );
  407. $ups = unpack('C*', $str);
  408. if (!($aCnt = count($ups))) return true; // Empty string *is* valid UTF-8
  409. for ($i = 1; $i <= $aCnt;)
  410. {
  411. if (!($tbytes = $trailing_bytes[($b1 = $ups[$i++])])) continue;
  412. if ($tbytes == -1) return false;
  413. $first = true;
  414. while ($tbytes > 0 && $i <= $aCnt)
  415. {
  416. $cbyte = $ups[$i++];
  417. if (($cbyte & 0xC0) != 0x80) return false;
  418. if ($first)
  419. {
  420. switch ($b1)
  421. {
  422. case 0xE0:
  423. if ($cbyte < 0xA0) return false;
  424. break;
  425. case 0xED:
  426. if ($cbyte > 0x9F) return false;
  427. break;
  428. case 0xF0:
  429. if ($cbyte < 0x90) return false;
  430. break;
  431. case 0xF4:
  432. if ($cbyte > 0x8F) return false;
  433. break;
  434. default:
  435. break;
  436. }
  437. $first = false;
  438. }
  439. $tbytes--;
  440. }
  441. if ($tbytes) return false; // incomplete sequence at EOS
  442. }
  443. return true;
  444. }
  445. public function __destruct()
  446. {
  447. $this->deletedImportIDFile();
  448. }
  449. }
  450. ?>