PageRenderTime 46ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/csvlib.class.php

https://bitbucket.org/kudutest1/moodlegit
PHP | 542 lines | 273 code | 43 blank | 226 comment | 45 complexity | b59d7480c39aea77f49a991b1e2a858e MD5 | raw file
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * This is a one-line short description of the file
  18. *
  19. * You can have a rather longer description of the file as well,
  20. * if you like, and it can span multiple lines.
  21. *
  22. * @package core
  23. * @subpackage lib
  24. * @copyright Petr Skoda
  25. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  26. */
  27. defined('MOODLE_INTERNAL') || die();
  28. /**
  29. * Utitily class for importing of CSV files.
  30. * @copyright Petr Skoda
  31. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  32. * @package moodlecore
  33. */
  34. class csv_import_reader {
  35. /**
  36. * @var int import identifier
  37. */
  38. var $_iid;
  39. /**
  40. * @var string which script imports?
  41. */
  42. var $_type;
  43. /**
  44. * @var string|null Null if ok, error msg otherwise
  45. */
  46. var $_error;
  47. /**
  48. * @var array cached columns
  49. */
  50. var $_columns;
  51. /**
  52. * @var object file handle used during import
  53. */
  54. var $_fp;
  55. /**
  56. * Contructor
  57. *
  58. * @param int $iid import identifier
  59. * @param string $type which script imports?
  60. */
  61. function csv_import_reader($iid, $type) {
  62. $this->_iid = $iid;
  63. $this->_type = $type;
  64. }
  65. /**
  66. * Parse this content
  67. *
  68. * @global object
  69. * @global object
  70. * @param string $content passed by ref for memory reasons, unset after return
  71. * @param string $encoding content encoding
  72. * @param string $delimiter_name separator (comma, semicolon, colon, cfg)
  73. * @param string $column_validation name of function for columns validation, must have one param $columns
  74. * @param string $enclosure field wrapper. One character only.
  75. * @return bool false if error, count of data lines if ok; use get_error() to get error string
  76. */
  77. function load_csv_content(&$content, $encoding, $delimiter_name, $column_validation=null, $enclosure='"') {
  78. global $USER, $CFG;
  79. $this->close();
  80. $this->_error = null;
  81. $content = textlib::convert($content, $encoding, 'utf-8');
  82. // remove Unicode BOM from first line
  83. $content = textlib::trim_utf8_bom($content);
  84. // Fix mac/dos newlines
  85. $content = preg_replace('!\r\n?!', "\n", $content);
  86. // Remove any spaces or new lines at the end of the file.
  87. if ($delimiter_name == 'tab') {
  88. // trim() by default removes tabs from the end of content which is undesirable in a tab separated file.
  89. $content = trim($content, chr(0x20) . chr(0x0A) . chr(0x0D) . chr(0x00) . chr(0x0B));
  90. } else {
  91. $content = trim($content);
  92. }
  93. $csv_delimiter = csv_import_reader::get_delimiter($delimiter_name);
  94. // $csv_encode = csv_import_reader::get_encoded_delimiter($delimiter_name);
  95. // Create a temporary file and store the csv file there,
  96. // do not try using fgetcsv() because there is nothing
  97. // to split rows properly - fgetcsv() itself can not do it.
  98. $tempfile = tempnam(make_temp_directory('/cvsimport'), 'tmp');
  99. if (!$fp = fopen($tempfile, 'w+b')) {
  100. $this->_error = get_string('cannotsavedata', 'error');
  101. @unlink($tempfile);
  102. return false;
  103. }
  104. fwrite($fp, $content);
  105. fseek($fp, 0);
  106. // Create an array to store the imported data for error checking.
  107. $columns = array();
  108. // str_getcsv doesn't iterate through the csv data properly. It has
  109. // problems with line returns.
  110. while ($fgetdata = fgetcsv($fp, 0, $csv_delimiter, $enclosure)) {
  111. // Check to see if we have an empty line.
  112. if (count($fgetdata) == 1) {
  113. if ($fgetdata[0] !== null) {
  114. // The element has data. Add it to the array.
  115. $columns[] = $fgetdata;
  116. }
  117. } else {
  118. $columns[] = $fgetdata;
  119. }
  120. }
  121. $col_count = 0;
  122. // process header - list of columns
  123. if (!isset($columns[0])) {
  124. $this->_error = get_string('csvemptyfile', 'error');
  125. fclose($fp);
  126. unlink($tempfile);
  127. return false;
  128. } else {
  129. $col_count = count($columns[0]);
  130. }
  131. // Column validation.
  132. if ($column_validation) {
  133. $result = $column_validation($columns[0]);
  134. if ($result !== true) {
  135. $this->_error = $result;
  136. fclose($fp);
  137. unlink($tempfile);
  138. return false;
  139. }
  140. }
  141. $this->_columns = $columns[0]; // cached columns
  142. // check to make sure that the data columns match up with the headers.
  143. foreach ($columns as $rowdata) {
  144. if (count($rowdata) !== $col_count) {
  145. $this->_error = get_string('csvweirdcolumns', 'error');
  146. fclose($fp);
  147. unlink($tempfile);
  148. $this->cleanup();
  149. return false;
  150. }
  151. }
  152. $filename = $CFG->tempdir.'/csvimport/'.$this->_type.'/'.$USER->id.'/'.$this->_iid;
  153. $filepointer = fopen($filename, "w");
  154. // The information has been stored in csv format, as serialized data has issues
  155. // with special characters and line returns.
  156. $storedata = csv_export_writer::print_array($columns, ',', '"', true);
  157. fwrite($filepointer, $storedata);
  158. fclose($fp);
  159. unlink($tempfile);
  160. fclose($filepointer);
  161. $datacount = count($columns);
  162. return $datacount;
  163. }
  164. /**
  165. * Returns list of columns
  166. *
  167. * @return array
  168. */
  169. function get_columns() {
  170. if (isset($this->_columns)) {
  171. return $this->_columns;
  172. }
  173. global $USER, $CFG;
  174. $filename = $CFG->tempdir.'/csvimport/'.$this->_type.'/'.$USER->id.'/'.$this->_iid;
  175. if (!file_exists($filename)) {
  176. return false;
  177. }
  178. $fp = fopen($filename, "r");
  179. $line = fgetcsv($fp);
  180. fclose($fp);
  181. if ($line === false) {
  182. return false;
  183. }
  184. $this->_columns = $line;
  185. return $this->_columns;
  186. }
  187. /**
  188. * Init iterator.
  189. *
  190. * @global object
  191. * @global object
  192. * @return bool Success
  193. */
  194. function init() {
  195. global $CFG, $USER;
  196. if (!empty($this->_fp)) {
  197. $this->close();
  198. }
  199. $filename = $CFG->tempdir.'/csvimport/'.$this->_type.'/'.$USER->id.'/'.$this->_iid;
  200. if (!file_exists($filename)) {
  201. return false;
  202. }
  203. if (!$this->_fp = fopen($filename, "r")) {
  204. return false;
  205. }
  206. //skip header
  207. return (fgetcsv($this->_fp) !== false);
  208. }
  209. /**
  210. * Get next line
  211. *
  212. * @return mixed false, or an array of values
  213. */
  214. function next() {
  215. if (empty($this->_fp) or feof($this->_fp)) {
  216. return false;
  217. }
  218. if ($ser = fgetcsv($this->_fp)) {
  219. return $ser;
  220. } else {
  221. return false;
  222. }
  223. }
  224. /**
  225. * Release iteration related resources
  226. *
  227. * @return void
  228. */
  229. function close() {
  230. if (!empty($this->_fp)) {
  231. fclose($this->_fp);
  232. $this->_fp = null;
  233. }
  234. }
  235. /**
  236. * Get last error
  237. *
  238. * @return string error text of null if none
  239. */
  240. function get_error() {
  241. return $this->_error;
  242. }
  243. /**
  244. * Cleanup temporary data
  245. *
  246. * @global object
  247. * @global object
  248. * @param boolean $full true means do a full cleanup - all sessions for current user, false only the active iid
  249. */
  250. function cleanup($full=false) {
  251. global $USER, $CFG;
  252. if ($full) {
  253. @remove_dir($CFG->tempdir.'/csvimport/'.$this->_type.'/'.$USER->id);
  254. } else {
  255. @unlink($CFG->tempdir.'/csvimport/'.$this->_type.'/'.$USER->id.'/'.$this->_iid);
  256. }
  257. }
  258. /**
  259. * Get list of cvs delimiters
  260. *
  261. * @return array suitable for selection box
  262. */
  263. static function get_delimiter_list() {
  264. global $CFG;
  265. $delimiters = array('comma'=>',', 'semicolon'=>';', 'colon'=>':', 'tab'=>'\\t');
  266. if (isset($CFG->CSV_DELIMITER) and strlen($CFG->CSV_DELIMITER) === 1 and !in_array($CFG->CSV_DELIMITER, $delimiters)) {
  267. $delimiters['cfg'] = $CFG->CSV_DELIMITER;
  268. }
  269. return $delimiters;
  270. }
  271. /**
  272. * Get delimiter character
  273. *
  274. * @param string separator name
  275. * @return string delimiter char
  276. */
  277. static function get_delimiter($delimiter_name) {
  278. global $CFG;
  279. switch ($delimiter_name) {
  280. case 'colon': return ':';
  281. case 'semicolon': return ';';
  282. case 'tab': return "\t";
  283. case 'cfg': if (isset($CFG->CSV_DELIMITER)) { return $CFG->CSV_DELIMITER; } // no break; fall back to comma
  284. case 'comma': return ',';
  285. default : return ','; // If anything else comes in, default to comma.
  286. }
  287. }
  288. /**
  289. * Get encoded delimiter character
  290. *
  291. * @global object
  292. * @param string separator name
  293. * @return string encoded delimiter char
  294. */
  295. static function get_encoded_delimiter($delimiter_name) {
  296. global $CFG;
  297. if ($delimiter_name == 'cfg' and isset($CFG->CSV_ENCODE)) {
  298. return $CFG->CSV_ENCODE;
  299. }
  300. $delimiter = csv_import_reader::get_delimiter($delimiter_name);
  301. return '&#'.ord($delimiter);
  302. }
  303. /**
  304. * Create new import id
  305. *
  306. * @global object
  307. * @param string who imports?
  308. * @return int iid
  309. */
  310. static function get_new_iid($type) {
  311. global $USER;
  312. $filename = make_temp_directory('csvimport/'.$type.'/'.$USER->id);
  313. // use current (non-conflicting) time stamp
  314. $iiid = time();
  315. while (file_exists($filename.'/'.$iiid)) {
  316. $iiid--;
  317. }
  318. return $iiid;
  319. }
  320. }
  321. /**
  322. * Utitily class for exporting of CSV files.
  323. * @copyright 2012 Adrian Greeve
  324. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  325. * @package core
  326. * @category csv
  327. */
  328. class csv_export_writer {
  329. /**
  330. * @var string $delimiter The name of the delimiter. Supported types(comma, tab, semicolon, colon, cfg)
  331. */
  332. var $delimiter;
  333. /**
  334. * @var string $csvenclosure How fields with spaces and commas are enclosed.
  335. */
  336. var $csvenclosure;
  337. /**
  338. * @var string $mimetype Mimetype of the file we are exporting.
  339. */
  340. var $mimetype;
  341. /**
  342. * @var string $filename The filename for the csv file to be downloaded.
  343. */
  344. var $filename;
  345. /**
  346. * @var string $path The directory path for storing the temporary csv file.
  347. */
  348. var $path;
  349. /**
  350. * @var resource $fp File pointer for the csv file.
  351. */
  352. protected $fp;
  353. /**
  354. * Constructor for the csv export reader
  355. *
  356. * @param string $delimiter The name of the character used to seperate fields. Supported types(comma, tab, semicolon, colon, cfg)
  357. * @param string $enclosure The character used for determining the enclosures.
  358. * @param string $mimetype Mime type of the file that we are exporting.
  359. */
  360. public function __construct($delimiter = 'comma', $enclosure = '"', $mimetype = 'application/download') {
  361. $this->delimiter = $delimiter;
  362. // Check that the enclosure is a single character.
  363. if (strlen($enclosure) == 1) {
  364. $this->csvenclosure = $enclosure;
  365. } else {
  366. $this->csvenclosure = '"';
  367. }
  368. $this->filename = "Moodle-data-export.csv";
  369. $this->mimetype = $mimetype;
  370. }
  371. /**
  372. * Set the file path to the temporary file.
  373. */
  374. protected function set_temp_file_path() {
  375. global $USER, $CFG;
  376. make_temp_directory('csvimport/' . $USER->id);
  377. $path = $CFG->tempdir . '/csvimport/' . $USER->id. '/' . $this->filename;
  378. // Check to see if the file exists, if so delete it.
  379. if (file_exists($path)) {
  380. unlink($path);
  381. }
  382. $this->path = $path;
  383. }
  384. /**
  385. * Add data to the temporary file in csv format
  386. *
  387. * @param array $row An array of values.
  388. */
  389. public function add_data($row) {
  390. if(!isset($this->path)) {
  391. $this->set_temp_file_path();
  392. $this->fp = fopen($this->path, 'w+');
  393. }
  394. $delimiter = csv_import_reader::get_delimiter($this->delimiter);
  395. fputcsv($this->fp, $row, $delimiter, $this->csvenclosure);
  396. }
  397. /**
  398. * Echos or returns a csv data line by line for displaying.
  399. *
  400. * @param bool $return Set to true to return a string with the csv data.
  401. * @return string csv data.
  402. */
  403. public function print_csv_data($return = false) {
  404. fseek($this->fp, 0);
  405. $returnstring = '';
  406. while (($content = fgets($this->fp)) !== false) {
  407. if (!$return){
  408. echo $content;
  409. } else {
  410. $returnstring .= $content;
  411. }
  412. }
  413. if ($return) {
  414. return $returnstring;
  415. }
  416. }
  417. /**
  418. * Set the filename for the uploaded csv file
  419. *
  420. * @param string $dataname The name of the module.
  421. * @param string $extenstion File extension for the file.
  422. */
  423. public function set_filename($dataname, $extension = '.csv') {
  424. $filename = clean_filename($dataname);
  425. $filename .= clean_filename('-' . gmdate("Ymd_Hi"));
  426. $filename .= clean_filename("-{$this->delimiter}_separated");
  427. $filename .= $extension;
  428. $this->filename = $filename;
  429. }
  430. /**
  431. * Output file headers to initialise the download of the file.
  432. */
  433. protected function send_header() {
  434. global $CFG;
  435. if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431
  436. header('Cache-Control: max-age=10');
  437. header('Pragma: ');
  438. } else { //normal http - prevent caching at all cost
  439. header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
  440. header('Pragma: no-cache');
  441. }
  442. header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
  443. header("Content-Type: $this->mimetype\n");
  444. header("Content-Disposition: attachment; filename=\"$this->filename\"");
  445. }
  446. /**
  447. * Download the csv file.
  448. */
  449. public function download_file() {
  450. $this->send_header();
  451. $this->print_csv_data();
  452. exit;
  453. }
  454. /**
  455. * Creates a file for downloading an array into a deliminated format.
  456. * This function is useful if you are happy with the defaults and all of your
  457. * information is in one array.
  458. *
  459. * @param string $filename The filename of the file being created.
  460. * @param array $records An array of information to be converted.
  461. * @param string $delimiter The name of the delimiter. Supported types(comma, tab, semicolon, colon, cfg)
  462. * @param string $enclosure How speical fields are enclosed.
  463. */
  464. public static function download_array($filename, array &$records, $delimiter = 'comma', $enclosure='"') {
  465. $csvdata = new csv_export_writer($delimiter, $enclosure);
  466. $csvdata->set_filename($filename);
  467. foreach ($records as $row) {
  468. $csvdata->add_data($row);
  469. }
  470. $csvdata->download_file();
  471. }
  472. /**
  473. * This will convert an array of values into a deliminated string.
  474. * Like the above function, this is for convenience.
  475. *
  476. * @param array $records An array of information to be converted.
  477. * @param string $delimiter The name of the delimiter. Supported types(comma, tab, semicolon, colon, cfg)
  478. * @param string $enclosure How speical fields are enclosed.
  479. * @param bool $return If true will return a string with the csv data.
  480. * @return string csv data.
  481. */
  482. public static function print_array(array &$records, $delimiter = 'comma', $enclosure = '"', $return = false) {
  483. $csvdata = new csv_export_writer($delimiter, $enclosure);
  484. foreach ($records as $row) {
  485. $csvdata->add_data($row);
  486. }
  487. $data = $csvdata->print_csv_data($return);
  488. if ($return) {
  489. return $data;
  490. }
  491. }
  492. /**
  493. * Make sure that everything is closed when we are finished.
  494. */
  495. public function __destruct() {
  496. fclose($this->fp);
  497. unlink($this->path);
  498. }
  499. }