handle = fopen($file, $mode); } /** * Writes info to the log * @param mixed $entries and string or an array to write to log * @access public */ public function log($entries) { if(is_string($entries)) { fwrite($this->handle, "Error: [" . date("d/M/Y H:i:s") . "] " . $entries . "\n"); } else { foreach($entries as $value) { fwrite($this->handle, "Error: [" . date("d/M/Y H:i:s") . "] " . $value . "\n"); } } } } /** * A simple database class that uses an active record approach to writing information to the database * @author Dennis Thompson * @license MIT * @since 1.0 */ class Database { private $db, $log; public function __construct($host, $user, $pass, $db, $log) { try { if(!is_object($log)) { throw new InvalidArgumentException('$log expected to be an object. Got ' . gettype($log), 2); } $this->log = &$log; } catch(InvalidArgumentException $e) { $this->log = new Log(Import::LOG_NAME); // create a new instance $this->log->log($e->message); } $this->db = new mysqli($host, $user, $pass, $db); } /** * Inserts information into the database using an active record approach and prepared statements * @param string $table the name of the table to write * @param array $data the array of data to write to the table * @access public * @throws InvalidArgumentException */ public function insert($table, $data) { if(!is_array($data)) { $msg = '$data expects to be an array or an object. Got ' . gettype($data); $this->log->log($msg); throw new InvalidArgumentException($msg, 1); } $handle = $this->db->prepare("INSERT INTO `" . $table . "` (room, mon, tue, wed, thu, fri, sat, sun) VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); // prepared statements are a safer way to send data to a database $handle->bind_param('ssssssss', $room, $mon, $tue, $wed, $thu, $fri, $sat, $sun); list($room, $mon, $tue, $wed, $thu, $fri, $sat, $sun) = $data; $handle->execute(); } /** * Inserts information into the database using active record approach * @param string $table * @param array $data a 2D array * @access public * @uses Database::insert() * @throws InvalidArgumentException */ public function insert_batch($table, $data) { if(!is_array($data)) { $msg = '$data expects to be an array or an object. Got ' . gettype($data); $this->log->log($msg); throw new InvalidArgumentException($msg, 1); } foreach($data as $value) { $this->insert($table, $value); } } } class Import { const LOG_NAME = "csv_import_error.log"; const REMOTE_URL = "http://example.com/"; const REMOTE_FILE = "file.csv"; const LOCAL_CSV_FILENAME = "local_csv.csv"; private $errors, $mail_on_failure, $to, $subject, $headers, $db, $log; public function __construct($mail_on_failure = true) { $this->log = new Log(self::LOG_NAME); $this->db = new Database("127.0.0.1", "root", "", "my_database", $this->log); // create a new database object $this->errors = array(); // initialize errors to an empty array $this->mail_on_failure = $mail_on_failure; if($this->mail_on_failure) { $this->to = "user@foo.com"; $this->subject = count($this->errors) . " Errors during import"; $this->headers = "From: " . $_SERVER["SERVER_NAME"] . "\r\n"; $this->headers .= "X-Mailer: PHP/" . phpversion(); } } /** * Change who the failure email is sent to * @access public * @since 1.0 */ public function to($to) { $this->to = $to; } /** * Change the subject line of the failure email * @access public * @since 1.0 */ public function subject($subject) { $this->subject = $subject; } /** * Change the headers of the failure email * @access public * @since 1.0 */ public function headers($headers) { $this->headers = $headers; } /** * Gets the errors for custom error handling * @return array * @access public * @since 1.0 */ public function getErrors() { return $this->errors; } /** * Parses the CSV file into an array and imports the file into the database * @param string [ $file = self::LOCAL_CSV_FILENAME ] * @param string [ $delimiter = "," ] * @param string [ $enclosure = "'" ] * @param string [ $escape = "\\" ] * @access public * @uses Database * @throws FileNotFoundException */ public function import($file = self::LOCAL_CSV_FILENAME, $delimiter = ",", $enclosure = "'", $escape = "\\") { if(!file_exists($file)) { $this->log->log($this->errors[] = "'" . $file . "' does not exist or the path is invalid"); $this->sendErrorEmail(); // force email before exception is thrown throw new FileNotFoundException($this->errors[count($this->errors) - 1]); } $handle = fopen($file, "r"); $data = fgetcsv($handle, 0, $delimiter, $enclosure, $escape); $this->db->insert_batch("times", array_chunk($data, 8)); } /** * Gets a file from a remote server and downloads it * @access public */ public function getRemoteFile() { if(function_exists("curl_version")) { $file = $this->getRemoteFileCurl(); $handle = fopen(self::LOCAL_CSV_FILENAME, "w"); fwrite($handle, $file); fclose($handle); } else { $this->getRemoteFileCopy(); } } /** * Sends email upon failure to import CSV * @access private */ private function sendErrorEmail() { $message = "Errors:\r\n"; for($i = 0; $i < count($this->errors); $i++) { $message .= "#" . $i . " " . $this->errors[$i] . "\r\n"; } if(mail($this->to, $this->subject, $message, $this->headers)) { $this->log->log("Success sending error report to " . $this->to); } else { $this->log->log($this->errors[] = "Error sending failure email. Issues are described below"); } } /** * Gets remote file and returns it's contents using cURL * @access private * @return string */ private function getRemoteFileCurl() { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, self::REMOTE_URL . self::REMOTE_FILE); curl_setopt($ch, CURLOPT_VERBOSE, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_AUTOREFERER, false); curl_setopt($ch, CURLOPT_REFERER, self::REMOTE_URL); curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); curl_setopt($ch, CURLOPT_HEADER, 0); $result = curl_exec($ch); curl_close($ch); return $result; } /** * Gets remote file and returns it's contents using copy * @throws Exception */ private function getRemoteFileCopy() { if(!copy(self::REMOTE_URL . self::REMOTE_FILE, "./" . self::LOCAL_CSV_FILENAME)) { $msg = "Failed to copy " . self::REMOTE_URL . self::REMOTE_FILE . " into " . self::LOCAL_CSV_FILENAME; $this->log->log($msg); $this->sendErrorEmail(); throw new Exception($msg, 1); } } } $foo = new Import(); $foo->getRemoteFile(); $foo->import();