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

/application/libraries/CSVImporter.php

http://github.com/ushahidi/Ushahidi_Web
PHP | 434 lines | 251 code | 53 blank | 130 comment | 45 complexity | 02d1d17713da4059e3995f171a4adc70 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. /**
  3. * CSV Report Importer Library
  4. *
  5. * Imports reports within CSV file referenced by filehandle.
  6. *
  7. * PHP version 5
  8. * LICENSE: This source file is subject to LGPL license
  9. * that is available through the world-wide-web at the following URI:
  10. * http://www.gnu.org/copyleft/lesser.html
  11. * @author Ushahidi Team <team@ushahidi.com>
  12. * @package Ushahidi - http://source.ushahididev.com
  13. * @copyright Ushahidi - http://www.ushahidi.com
  14. * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
  15. *
  16. */
  17. class CSVImporter {
  18. /**
  19. * Notices to be passed on successful data import
  20. * @var array
  21. */
  22. public $notices = array();
  23. /**
  24. * Errors to be passed on failed data import
  25. * @var array
  26. */
  27. public $errors = array();
  28. /**
  29. * Total number of reports within CSV file
  30. * @var int
  31. */
  32. public $totalreports = 0;
  33. /**
  34. * Total number of reports successfully imported
  35. * @var int
  36. */
  37. public $importedreports = 0;
  38. /**
  39. * Categories successfully imported
  40. * @var array
  41. */
  42. private $categories_added = array();
  43. /**
  44. * Reports successfully imported
  45. * @var array
  46. */
  47. private $incidents_added = array();
  48. /**
  49. * Incident persons successfully imported
  50. * @var array
  51. */
  52. private $incident_persons_added = array();
  53. /**
  54. * Custom form field responses successfully imported
  55. * @var array
  56. */
  57. private $incident_responses_added = array();
  58. /**
  59. * Incident locations successfully imported
  60. * @var array
  61. */
  62. private $locations_added = array();
  63. /**
  64. * Incident categories successfully imported
  65. * @var array
  66. */
  67. private $incident_categories_added = array();
  68. /**
  69. * Function to import CSV file referenced by the file handle
  70. * @param string $filehandle
  71. * @return bool
  72. */
  73. function import($file)
  74. {
  75. // Get contents of CSV file
  76. $data = file_get_contents($file);
  77. // Normalize new lines, replace ANY unicode new line with \n (should cover Mac OS9, Unix, Windows, etc)
  78. $replacedata = preg_replace('/\R/u', "\n", mb_convert_encoding($data, 'UTF-8'));
  79. // Check for preg error, and fall back to original data
  80. if (preg_last_error() !== PREG_NO_ERROR) {
  81. $replacedata = $data;
  82. }
  83. // Replace file content
  84. file_put_contents($file, $replacedata);
  85. if($filehandle = fopen($_FILES['uploadfile']['tmp_name'], 'r'))
  86. {
  87. $csvtable = new Csvtable($filehandle);
  88. // Set the required columns of the CSV file
  89. $requiredcolumns = array('INCIDENT TITLE','INCIDENT DATE');
  90. foreach ($requiredcolumns as $requiredcolumn)
  91. {
  92. // If the CSV file is missing any required column, return an error
  93. if (!$csvtable->hasColumn($requiredcolumn))
  94. {
  95. $this->errors[] = Kohana::lang('import.csv.required_column').'"'.$requiredcolumn.'"';
  96. }
  97. }
  98. if (count($this->errors))
  99. {
  100. return false;
  101. }
  102. // So we can assign category id to incidents, based on category title
  103. $this->existing_categories = ORM::factory('category')->select_list('category_title','id');
  104. //Since we capitalize the category names from the CSV file, we should also capitlize the
  105. //category titles here so we get case insensative behavior. For some reason users don't
  106. //always captilize the cateogry names as they enter them in
  107. $temp_cat = array();
  108. foreach($this->existing_categories as $title => $id)
  109. {
  110. $temp_cat[utf8::strtoupper($title)] = $id;
  111. // Add translated titles too
  112. $langs = Category_Lang_Model::category_langs($id);
  113. if (isset($langs[$id]))
  114. {
  115. foreach($langs[$id] as $l)
  116. {
  117. $temp_cat[utf8::strtoupper($l['category_title'])] = $id;
  118. }
  119. }
  120. }
  121. $this->existing_categories = $temp_cat;
  122. // So we can check if incident already exists in database
  123. $this->incident_ids = ORM::factory('incident')->select_list('id','id');
  124. $this->time = date("Y-m-d H:i:s",time());
  125. $rows = $csvtable->getRows();
  126. $this->totalreports = count($rows);
  127. $this->rownumber = 0;
  128. // Loop through CSV rows
  129. foreach($rows as $row)
  130. {
  131. $this->rownumber++;
  132. if (isset($row['#']) AND isset($this->incident_ids[$row['#']]))
  133. {
  134. $this->notices[] = Kohana::lang('import.incident_exists').$row['#'];
  135. }
  136. else
  137. {
  138. if ($this->import_report($row))
  139. {
  140. $this->importedreports++;
  141. }
  142. else
  143. {
  144. $this->rollback();
  145. return false;
  146. }
  147. }
  148. }
  149. }
  150. else
  151. {
  152. $this->errors[] = Kohana::lang('ui_admin.file_open_error');
  153. }
  154. // If we have errors, return FALSE, else TRUE
  155. return count($this->errors) === 0;
  156. }
  157. /**
  158. * Function to undo import of reports
  159. */
  160. function rollback()
  161. {
  162. if (count($this->incidents_added)) ORM::factory('incident')->delete_all($this->incidents_added);
  163. if (count($this->categories_added)) ORM::factory('category')->delete_all($this->categories_added);
  164. if (count($this->locations_added)) ORM::factory('location')->delete_all($this->locations_added);
  165. if (count($this->incident_categories_added)) ORM::factory('incident_category')->delete_all($this->incident_categories_added);
  166. if (count($this->incident_persons_added)) ORM::factory('incident_person')->delete_all($this->incident_persons_added);
  167. if (count($this->incident_responses_added)) ORM::factory('form_response')->delete_all($this->incident_responses_added);
  168. }
  169. /**
  170. * Function to import a report form a row in the CSV file
  171. * @param array $row
  172. * @return bool
  173. */
  174. function import_report($row)
  175. {
  176. // If the date is not in proper date format
  177. if (!strtotime($row['INCIDENT DATE']))
  178. {
  179. $this->errors[] = Kohana::lang('import.incident_date').($this->rownumber+1).': '.$row['INCIDENT DATE'];
  180. }
  181. // If a value of Yes or No is NOT set for approval status for the imported row
  182. if (isset($row["APPROVED"]) AND !in_array(utf8::strtoupper($row["APPROVED"]),array('NO','YES')))
  183. {
  184. $this->errors[] = Kohana::lang('import.csv.approved').($this->rownumber+1);
  185. }
  186. // If a value of Yes or No is NOT set for verified status for the imported row
  187. if (isset($row["VERIFIED"]) AND !in_array(utf8::strtoupper($row["VERIFIED"]),array('NO','YES')))
  188. {
  189. $this->errors[] = Kohana::lang('import.csv.verified').($this->rownumber+1);
  190. }
  191. if (count($this->errors))
  192. {
  193. return false;
  194. }
  195. // STEP 1: SAVE LOCATION
  196. if (isset($row['LOCATION']))
  197. {
  198. $location = new Location_Model();
  199. $location->location_name = isset($row['LOCATION']) ? $row['LOCATION'] : '';
  200. // For Geocoding purposes
  201. $location_geocoded = map::geocode($location->location_name);
  202. // If we have LATITUDE and LONGITUDE use those
  203. if ( isset($row['LATITUDE']) AND isset($row['LONGITUDE']) )
  204. {
  205. $location->latitude = isset($row['LATITUDE']) ? $row['LATITUDE'] : 0;
  206. $location->longitude = isset($row['LONGITUDE']) ? $row['LONGITUDE'] : 0;
  207. }
  208. // Otherwise, get geocoded lat/lon values
  209. else
  210. {
  211. $location->latitude = $location_geocoded ? $location_geocoded['latitude'] : 0;
  212. $location->longitude = $location_geocoded ? $location_geocoded['longitude'] : 0;
  213. }
  214. $location->country_id = $location_geocoded ? $location_geocoded['country_id'] : 0;
  215. $location->location_date = $this->time;
  216. $location->save();
  217. $this->locations_added[] = $location->id;
  218. }
  219. // STEP 2: SAVE INCIDENT
  220. $incident = new Incident_Model();
  221. $incident->location_id = isset($row['LOCATION']) ? $location->id : 0;
  222. $incident->user_id = 0;
  223. $incident->form_id = (isset($row['FORM #']) AND Form_Model::is_valid_form($row['FORM #'])) ? $row['FORM #'] : 1;
  224. $incident->incident_title = $row['INCIDENT TITLE'];
  225. $incident->incident_description = isset($row['DESCRIPTION']) ? $row['DESCRIPTION'] : '';
  226. $incident->incident_date = date("Y-m-d H:i:s",strtotime($row['INCIDENT DATE']));
  227. $incident->incident_dateadd = $this->time;
  228. $incident->incident_active = (isset($row['APPROVED']) AND utf8::strtoupper($row['APPROVED']) == 'YES') ? 1 : 0;
  229. $incident->incident_verified = (isset($row['VERIFIED']) AND utf8::strtoupper($row['VERIFIED']) == 'YES') ? 1 :0;
  230. $incident->save();
  231. $this->incidents_added[] = $incident->id;
  232. // STEP 3: Save Personal Information
  233. if(isset($row['FIRST NAME']) OR isset($row['LAST NAME']) OR isset($row['EMAIL']))
  234. {
  235. $person = new Incident_Person_Model();
  236. $person->incident_id = $incident->id;
  237. $person->person_first = isset($row['FIRST NAME']) ? $row['FIRST NAME'] : '';
  238. $person->person_last = isset($row['LAST NAME']) ? $row['LAST NAME'] : '';
  239. $person->person_email = (isset($row['EMAIL']) AND valid::email($row['EMAIL']))? $row['EMAIL'] : '';
  240. $person->person_date = date("Y-m-d H:i:s",time());
  241. // Make sure that you're not importing an empty record i.e at least one field has been recorded
  242. // If all fields are empty i.e you have an empty record, don't save
  243. if(!empty($person->person_first) OR !empty($person->person_last) OR !empty($person->person_email))
  244. {
  245. $person->save();
  246. // Add to array of incident persons added
  247. $this->incident_persons_added[] = $person->id;
  248. }
  249. }
  250. // STEP 4: SAVE CATEGORIES
  251. // If CATEGORY column exists
  252. if (isset($row['CATEGORY']))
  253. {
  254. $categorynames = explode(',',trim($row['CATEGORY']));
  255. // Trim whitespace from array values
  256. $categorynames = array_map('trim',$categorynames);
  257. // Get rid of duplicate category entries in a row
  258. $categories = array_unique(array_map('strtolower', $categorynames));
  259. // Add categories to incident
  260. foreach ($categories as $categoryname)
  261. {
  262. // Convert the first string character of the category name to Uppercase
  263. $categoryname = utf8::ucfirst($categoryname);
  264. // For purposes of adding an entry into the incident_category table
  265. $incident_category = new Incident_Category_Model();
  266. $incident_category->incident_id = $incident->id;
  267. // If category name exists, add entry in incident_category table
  268. if($categoryname != '')
  269. {
  270. // Check if the category exists (made sure to convert to uppercase for comparison)
  271. if (!isset($this->existing_categories[utf8::strtoupper($categoryname)]))
  272. {
  273. $this->notices[] = Kohana::lang('import.new_category').$categoryname;
  274. $category = new Category_Model;
  275. $category->category_title = $categoryname;
  276. // We'll just use black for now. Maybe something random?
  277. $category->category_color = '000000';
  278. // because all current categories are of type '5'
  279. $category->category_visible = 1;
  280. $category->category_description = $categoryname;
  281. $category->category_position = count($this->existing_categories);
  282. $category->save();
  283. $this->categories_added[] = $category->id;
  284. // Now category_id is known: This time, and for the rest of the import.
  285. $this->existing_categories[utf8::strtoupper($categoryname)] = $category->id;
  286. }
  287. $incident_category->category_id = $this->existing_categories[utf8::strtoupper($categoryname)];
  288. $incident_category->save();
  289. $this->incident_categories_added[] = $incident_category->id;
  290. }
  291. }
  292. }
  293. // STEP 5: Save Custom form fields responses
  294. // Check for form_id
  295. $form_id = (isset($row['FORM #']) AND Form_Model::is_valid_form($row['FORM #'])) ? $row['FORM #'] : 1;
  296. // Get custom form fields for this particular form
  297. $custom_titles = customforms::get_custom_form_fields('',$form_id,false);
  298. // Do custom form fields exist on this deployment?
  299. if (!empty($custom_titles))
  300. {
  301. foreach($custom_titles as $field_name)
  302. {
  303. // Check if the column exists in the CSV
  304. $rowname = utf8::strtoupper($field_name['field_name']);
  305. if(isset($row[$rowname.'-'.$form_id]))
  306. {
  307. $response = $row[$rowname.'-'.$form_id];
  308. // Grab field_id and field_type
  309. $field_id = $field_name['field_id'];
  310. $field_type = $field_name['field_type'];
  311. // Initialize form response model
  312. $form_response = new Form_Response_Model();
  313. $form_response->incident_id = $incident->id;
  314. $form_response->form_field_id = $field_id;
  315. // If form response exists
  316. if($response != '')
  317. {
  318. /* Handling case sensitivity issues with custom form field upload */
  319. // Check if the field is a radio button, checkbox OR dropdown field
  320. if ($field_type == '5' OR $field_type == '6' OR $field_type =='7')
  321. {
  322. // Get field option values
  323. $field_values = $field_name['field_default'];
  324. // Split field options into individual values
  325. $options = explode(",", $field_values);
  326. // Since radio button and dropdown fields take single responses
  327. if ($field_type == '5' OR $field_type == '7')
  328. {
  329. foreach ($options as $option)
  330. {
  331. // Carry out a case insensitive comparison between individual field options and csv response
  332. // If there's a match, store field option value from the db
  333. if (strcasecmp($option, $response) == 0)
  334. {
  335. $form_response->form_response = $option;
  336. }
  337. }
  338. }
  339. // For checkboxes, which accomodate multiple responses
  340. if ($field_type == '6')
  341. {
  342. // Split user responses into single values
  343. $csvresponses = explode(",", $response);
  344. $values = array();
  345. foreach ($options as $option)
  346. {
  347. foreach ($csvresponses as $csvresponse)
  348. {
  349. // Carry out a case insensitive comparison between individual field options and csv response
  350. // If there's a match
  351. if(strcasecmp($option, $csvresponse) == 0)
  352. {
  353. // Store field option value from the db
  354. $values[] = $option;
  355. }
  356. }
  357. }
  358. // Concatenate checkbox values into a string, separated by a comma
  359. $form_response->form_response = implode(",", $values);
  360. }
  361. }
  362. // For all other form fields apart from the three mentioned above
  363. else
  364. {
  365. $form_response->form_response = $response;
  366. }
  367. // If form_response is provided based on conditions set above, Save the form response
  368. if ($form_response->form_response != '')
  369. {
  370. $form_response->save();
  371. // Add to array of field responses added
  372. $this->incident_responses_added[] = $form_response->id;
  373. }
  374. }
  375. }
  376. }
  377. }
  378. return true;
  379. }
  380. }
  381. ?>