-
Analyzing your requirements and generating your website…
-
+
+
+ Password
+
+
+
+ Select your role
+ Siswa (Student)
+ Wali Kelas (Homeroom Teacher)
+ Admin
+
+ Role
+
+
+
+
+ Remember me
+
+
+
Sign in
+
+ Forgot password?
+
+
© . All rights reserved.
+
-
- Page updated: = htmlspecialchars($now) ?> (UTC)
-
-
-
+
\ No newline at end of file
diff --git a/logout.php b/logout.php
new file mode 100644
index 0000000..85facf7
--- /dev/null
+++ b/logout.php
@@ -0,0 +1,5 @@
+prepare("SELECT * FROM password_resets WHERE token = ? AND expires_at > NOW()");
+ $stmt->execute([$token]);
+ $reset_request = $stmt->fetch();
+
+ if ($reset_request) {
+ $show_form = true;
+ } else {
+ $message = 'Invalid or expired password reset token.';
+ }
+ } catch (PDOException $e) {
+ $message = 'Database error: ' . $e->getMessage();
+ }
+}
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $password = $_POST['password'] ?? '';
+ $password_confirm = $_POST['password_confirm'] ?? '';
+
+ if (empty($password) || empty($password_confirm)) {
+ $message = 'Please enter and confirm your new password.';
+ } elseif ($password !== $password_confirm) {
+ $message = 'Passwords do not match.';
+ } else {
+ try {
+ $pdo = db();
+ $stmt = $pdo->prepare("SELECT * FROM password_resets WHERE token = ? AND expires_at > NOW()");
+ $stmt->execute([$token]);
+ $reset_request = $stmt->fetch();
+
+ if ($reset_request) {
+ $email = $reset_request['email'];
+ $hashed_password = password_hash($password, PASSWORD_DEFAULT);
+
+ // Update user's password
+ $stmt = $pdo->prepare("UPDATE users SET password = ? WHERE email = ?");
+ $stmt->execute([$hashed_password, $email]);
+
+ // Delete the reset token
+ $stmt = $pdo->prepare("DELETE FROM password_resets WHERE token = ?");
+ $stmt->execute([$token]);
+
+ $message = 'Your password has been reset successfully. You can now
login with your new password.';
+ $show_form = false;
+ } else {
+ $message = 'Invalid or expired password reset token.';
+ }
+ } catch (PDOException $e) {
+ $message = 'Database error: ' . $e->getMessage();
+ }
+ }
+}
+
+require_once __DIR__ . '/includes/header.php';
+?>
+
+
+
+
+
Reset Password
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/student_dashboard.php b/student_dashboard.php
new file mode 100644
index 0000000..308ddb1
--- /dev/null
+++ b/student_dashboard.php
@@ -0,0 +1,103 @@
+prepare("SELECT * FROM leave_requests WHERE student_id = ? ORDER BY created_at DESC");
+ $stmt->execute([$_SESSION['user_id']]);
+ $leave_requests = $stmt->fetchAll();
+} catch (PDOException $e) {
+ // handle error
+}
+
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+
Submit Leave Request
+
+
+
+
My Leave Requests
+
+
+
+ Leave Type
+ Start Date
+ End Date
+ Status
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/submit_leave_request.php b/submit_leave_request.php
new file mode 100644
index 0000000..097352c
--- /dev/null
+++ b/submit_leave_request.php
@@ -0,0 +1,52 @@
+prepare("INSERT INTO leave_requests (student_id, leave_type, start_date, end_date, reason, attachment_path) VALUES (?, ?, ?, ?, ?, ?)");
+ $stmt->execute([$student_id, $leave_type, $start_date, $end_date, $reason, $attachment_path]);
+
+ // Send email to teacher
+ $teacher_email = 'teacher@example.com'; // Hardcoded for now
+ $subject = 'New Leave Request from ' . $_SESSION['user_full_name'];
+ $body = "
A new leave request has been submitted by {" . $_SESSION['user_full_name'] . "}.
\n
Leave Type: {" . $leave_type . "}
\n
Start Date: {" . $start_date . "}
\n
End Date: {" . $end_date . "}
\n
Reason: {" . $reason . "}
\n
Please login to the dashboard to approve or reject this request.
";
+ MailService::sendMail($teacher_email, $subject, $body);
+
+ header('Location: student_dashboard.php?success=1');
+ exit;
+ } catch (PDOException $e) {
+ die('Database error: ' . $e->getMessage());
+ }
+}
\ No newline at end of file
diff --git a/teacher_dashboard.php b/teacher_dashboard.php
new file mode 100644
index 0000000..3e85860
--- /dev/null
+++ b/teacher_dashboard.php
@@ -0,0 +1,75 @@
+prepare("SELECT leave_requests.*, users.full_name AS student_name FROM leave_requests JOIN users ON leave_requests.student_id = users.id WHERE leave_requests.status = 'pending' ORDER BY leave_requests.created_at DESC");
+ $stmt->execute();
+ $leave_requests = $stmt->fetchAll();
+} catch (PDOException $e) {
+ // handle error
+}
+
+?>
+
+
+
+
+
+
+
Pending Leave Requests
+
+
+
+ Student Name
+ Leave Type
+ Start Date
+ End Date
+ Reason
+ Attachment
+ Action
+
+
+
+
+
+
+
+
+
+
+
+
+ View Attachment
+
+ No Attachment
+
+
+
+ Approve
+ Reject
+
+
+
+
+
+
+
+
+
+
diff --git a/update_leave_status.php b/update_leave_status.php
new file mode 100644
index 0000000..6eb3f73
--- /dev/null
+++ b/update_leave_status.php
@@ -0,0 +1,85 @@
+prepare("UPDATE leave_requests SET status = ? WHERE id = ?");
+ $stmt->execute([$new_status, $leave_request_id]);
+
+ // Get student email
+ $stmt = $pdo->prepare("SELECT users.email, users.full_name FROM leave_requests JOIN users ON leave_requests.student_id = users.id WHERE leave_requests.id = ?");
+ $stmt->execute([$leave_request_id]);
+ $student = $stmt->fetch();
+
+ if ($student) {
+ $student_email = $student['email'];
+ $student_name = $student['full_name'];
+
+ if ($new_status === 'approved_by_teacher') {
+ // Notify admin
+ $admin_email = 'admin@example.com'; // Hardcoded for now
+ $subject = 'Leave Request Approved by Teacher';
+ $body = "
The leave request for {$student_name} has been approved by the teacher and is waiting for your final approval.
Please login to the dashboard to review the request.
";
+ MailService::sendMail($admin_email, $subject, $body);
+
+ // Notify student
+ $subject_student = 'Your Leave Request has been updated';
+ $body_student = "
Your leave request has been approved by your teacher and is now pending final approval from the admin.
";
+ MailService::sendMail($student_email, $subject_student, $body_student);
+
+ } elseif ($new_status === 'rejected_by_teacher') {
+ // Notify student
+ $subject_student = 'Your Leave Request has been updated';
+ $body_student = "
Your leave request has been rejected by your teacher.
";
+ MailService::sendMail($student_email, $subject_student, $body_student);
+
+ } elseif ($new_status === 'approved_by_admin') {
+ // Notify student
+ $subject_student = 'Your Leave Request has been approved';
+ $body_student = "
Your leave request has been approved by the admin.
";
+ MailService::sendMail($student_email, $subject_student, $body_student);
+
+ } elseif ($new_status === 'rejected_by_admin') {
+ // Notify student
+ $subject_student = 'Your Leave Request has been rejected';
+ $body_student = "
Your leave request has been rejected by the admin.
";
+ MailService::sendMail($student_email, $subject_student, $body_student);
+ }
+ }
+
+ if ($_SESSION['user_role'] === 'teacher') {
+ header('Location: teacher_dashboard.php');
+ } elseif ($_SESSION['user_role'] === 'admin') {
+ header('Location: admin_dashboard.php');
+ }
+ exit;
+ } catch (PDOException $e) {
+ die('Database error: ' . $e->getMessage());
+ }
+}
diff --git a/vendor/autoload.php b/vendor/autoload.php
new file mode 100644
index 0000000..6a04776
--- /dev/null
+++ b/vendor/autoload.php
@@ -0,0 +1,22 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @see https://www.php-fig.org/psr/psr-0/
+ * @see https://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ /** @var \Closure(string):void */
+ private static $includeFile;
+
+ /** @var string|null */
+ private $vendorDir;
+
+ // PSR-4
+ /**
+ * @var array>
+ */
+ private $prefixLengthsPsr4 = array();
+ /**
+ * @var array>
+ */
+ private $prefixDirsPsr4 = array();
+ /**
+ * @var list
+ */
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ /**
+ * List of PSR-0 prefixes
+ *
+ * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
+ *
+ * @var array>>
+ */
+ private $prefixesPsr0 = array();
+ /**
+ * @var list
+ */
+ private $fallbackDirsPsr0 = array();
+
+ /** @var bool */
+ private $useIncludePath = false;
+
+ /**
+ * @var array
+ */
+ private $classMap = array();
+
+ /** @var bool */
+ private $classMapAuthoritative = false;
+
+ /**
+ * @var array
+ */
+ private $missingClasses = array();
+
+ /** @var string|null */
+ private $apcuPrefix;
+
+ /**
+ * @var array
+ */
+ private static $registeredLoaders = array();
+
+ /**
+ * @param string|null $vendorDir
+ */
+ public function __construct($vendorDir = null)
+ {
+ $this->vendorDir = $vendorDir;
+ self::initializeIncludeClosure();
+ }
+
+ /**
+ * @return array>
+ */
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+ }
+
+ return array();
+ }
+
+ /**
+ * @return array>
+ */
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ /**
+ * @return list
+ */
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ /**
+ * @return list
+ */
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ /**
+ * @return array Array of classname => path
+ */
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ *
+ * @return void
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param list|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @return void
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ $paths = (array) $paths;
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ $paths = (array) $paths;
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param list|string $paths The PSR-0 base directories
+ *
+ * @return void
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ *
+ * @return void
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ *
+ * @return void
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ *
+ * @return void
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ *
+ * @return void
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+ if (null === $this->vendorDir) {
+ return;
+ }
+
+ if ($prepend) {
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+ } else {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ self::$registeredLoaders[$this->vendorDir] = $this;
+ }
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ *
+ * @return void
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+
+ if (null !== $this->vendorDir) {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ }
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return true|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ $includeFile = self::$includeFile;
+ $includeFile($file);
+
+ return true;
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ /**
+ * Returns the currently registered loaders keyed by their corresponding vendor directories.
+ *
+ * @return array
+ */
+ public static function getRegisteredLoaders()
+ {
+ return self::$registeredLoaders;
+ }
+
+ /**
+ * @param string $class
+ * @param string $ext
+ * @return string|false
+ */
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return void
+ */
+ private static function initializeIncludeClosure()
+ {
+ if (self::$includeFile !== null) {
+ return;
+ }
+
+ /**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ *
+ * @param string $file
+ * @return void
+ */
+ self::$includeFile = \Closure::bind(static function($file) {
+ include $file;
+ }, null, null);
+ }
+}
diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php
new file mode 100644
index 0000000..2052022
--- /dev/null
+++ b/vendor/composer/InstalledVersions.php
@@ -0,0 +1,396 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ *
+ * @final
+ */
+class InstalledVersions
+{
+ /**
+ * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
+ * @internal
+ */
+ private static $selfDir = null;
+
+ /**
+ * @var mixed[]|null
+ * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null
+ */
+ private static $installed;
+
+ /**
+ * @var bool
+ */
+ private static $installedIsLocalDir;
+
+ /**
+ * @var bool|null
+ */
+ private static $canGetVendors;
+
+ /**
+ * @var array[]
+ * @psalm-var array}>
+ */
+ private static $installedByVendor = array();
+
+ /**
+ * Returns a list of all package names which are present, either by being installed, replaced or provided
+ *
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackages()
+ {
+ $packages = array();
+ foreach (self::getInstalled() as $installed) {
+ $packages[] = array_keys($installed['versions']);
+ }
+
+ if (1 === \count($packages)) {
+ return $packages[0];
+ }
+
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+ }
+
+ /**
+ * Returns a list of all package names with a specific type e.g. 'library'
+ *
+ * @param string $type
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackagesByType($type)
+ {
+ $packagesByType = array();
+
+ foreach (self::getInstalled() as $installed) {
+ foreach ($installed['versions'] as $name => $package) {
+ if (isset($package['type']) && $package['type'] === $type) {
+ $packagesByType[] = $name;
+ }
+ }
+ }
+
+ return $packagesByType;
+ }
+
+ /**
+ * Checks whether the given package is installed
+ *
+ * This also returns true if the package name is provided or replaced by another package
+ *
+ * @param string $packageName
+ * @param bool $includeDevRequirements
+ * @return bool
+ */
+ public static function isInstalled($packageName, $includeDevRequirements = true)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (isset($installed['versions'][$packageName])) {
+ return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether the given package satisfies a version constraint
+ *
+ * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+ *
+ * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+ *
+ * @param VersionParser $parser Install composer/semver to have access to this class and functionality
+ * @param string $packageName
+ * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+ * @return bool
+ */
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
+ {
+ $constraint = $parser->parseConstraints((string) $constraint);
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+ return $provided->matches($constraint);
+ }
+
+ /**
+ * Returns a version constraint representing all the range(s) which are installed for a given package
+ *
+ * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+ * whether a given version of a package is installed, and not just whether it exists
+ *
+ * @param string $packageName
+ * @return string Version constraint usable with composer/semver
+ */
+ public static function getVersionRanges($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ $ranges = array();
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+ }
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+ }
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+ }
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+ }
+
+ return implode(' || ', $ranges);
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getPrettyVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['pretty_version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+ */
+ public static function getReference($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['reference'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['reference'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+ */
+ public static function getInstallPath($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @return array
+ * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
+ */
+ public static function getRootPackage()
+ {
+ $installed = self::getInstalled();
+
+ return $installed[0]['root'];
+ }
+
+ /**
+ * Returns the raw installed.php data for custom implementations
+ *
+ * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+ * @return array[]
+ * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}
+ */
+ public static function getRawData()
+ {
+ @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ self::$installed = include __DIR__ . '/installed.php';
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ return self::$installed;
+ }
+
+ /**
+ * Returns the raw data of all installed.php which are currently loaded for custom implementations
+ *
+ * @return array[]
+ * @psalm-return list}>
+ */
+ public static function getAllRawData()
+ {
+ return self::getInstalled();
+ }
+
+ /**
+ * Lets you reload the static array from another file
+ *
+ * This is only useful for complex integrations in which a project needs to use
+ * this class but then also needs to execute another project's autoloader in process,
+ * and wants to ensure both projects have access to their version of installed.php.
+ *
+ * A typical case would be PHPUnit, where it would need to make sure it reads all
+ * the data it needs from this class, then call reload() with
+ * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+ * the project in which it runs can then also use this class safely, without
+ * interference between PHPUnit's dependencies and the project's dependencies.
+ *
+ * @param array[] $data A vendor/composer/installed.php data set
+ * @return void
+ *
+ * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data
+ */
+ public static function reload($data)
+ {
+ self::$installed = $data;
+ self::$installedByVendor = array();
+
+ // when using reload, we disable the duplicate protection to ensure that self::$installed data is
+ // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
+ // so we have to assume it does not, and that may result in duplicate data being returned when listing
+ // all installed packages for example
+ self::$installedIsLocalDir = false;
+ }
+
+ /**
+ * @return string
+ */
+ private static function getSelfDir()
+ {
+ if (self::$selfDir === null) {
+ self::$selfDir = strtr(__DIR__, '\\', '/');
+ }
+
+ return self::$selfDir;
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return list}>
+ */
+ private static function getInstalled()
+ {
+ if (null === self::$canGetVendors) {
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+ }
+
+ $installed = array();
+ $copiedLocalDir = false;
+
+ if (self::$canGetVendors) {
+ $selfDir = self::getSelfDir();
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+ $vendorDir = strtr($vendorDir, '\\', '/');
+ if (isset(self::$installedByVendor[$vendorDir])) {
+ $installed[] = self::$installedByVendor[$vendorDir];
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
+ $required = require $vendorDir.'/composer/installed.php';
+ self::$installedByVendor[$vendorDir] = $required;
+ $installed[] = $required;
+ if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
+ self::$installed = $required;
+ self::$installedIsLocalDir = true;
+ }
+ }
+ if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
+ $copiedLocalDir = true;
+ }
+ }
+ }
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
+ $required = require __DIR__ . '/installed.php';
+ self::$installed = $required;
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ if (self::$installed !== array() && !$copiedLocalDir) {
+ $installed[] = self::$installed;
+ }
+
+ return $installed;
+ }
+}
diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE
new file mode 100644
index 0000000..f27399a
--- /dev/null
+++ b/vendor/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
new file mode 100644
index 0000000..0fb0a2c
--- /dev/null
+++ b/vendor/composer/autoload_classmap.php
@@ -0,0 +1,10 @@
+ $vendorDir . '/composer/InstalledVersions.php',
+);
diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php
new file mode 100644
index 0000000..15a2ff3
--- /dev/null
+++ b/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,9 @@
+ array($vendorDir . '/parsecsv/php-parsecsv/src'),
+);
diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php
new file mode 100644
index 0000000..abdaa0e
--- /dev/null
+++ b/vendor/composer/autoload_real.php
@@ -0,0 +1,38 @@
+register(true);
+
+ return $loader;
+ }
+}
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
new file mode 100644
index 0000000..ceea6e5
--- /dev/null
+++ b/vendor/composer/autoload_static.php
@@ -0,0 +1,36 @@
+
+ array (
+ 'ParseCsv\\' => 9,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'ParseCsv\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/parsecsv/php-parsecsv/src',
+ ),
+ );
+
+ public static $classMap = array (
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInitf6815287adc097f2e9e30b4ea978d3c7::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInitf6815287adc097f2e9e30b4ea978d3c7::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInitf6815287adc097f2e9e30b4ea978d3c7::$classMap;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
new file mode 100644
index 0000000..fbc9db9
--- /dev/null
+++ b/vendor/composer/installed.json
@@ -0,0 +1,73 @@
+{
+ "packages": [
+ {
+ "name": "parsecsv/php-parsecsv",
+ "version": "1.3.2",
+ "version_normalized": "1.3.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/parsecsv/parsecsv-for-php.git",
+ "reference": "2d6236cae09133e0533d34ed45ba1e1ecafffebb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/parsecsv/parsecsv-for-php/zipball/2d6236cae09133e0533d34ed45ba1e1ecafffebb",
+ "reference": "2d6236cae09133e0533d34ed45ba1e1ecafffebb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6",
+ "squizlabs/php_codesniffer": "^3.5"
+ },
+ "suggest": {
+ "illuminate/support": "Fluent array interface for map functions"
+ },
+ "time": "2021-11-07T14:15:46+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "ParseCsv\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jim Myhrberg",
+ "email": "contact@jimeh.me"
+ },
+ {
+ "name": "William Knauss",
+ "email": "will.knauss@gmail.com"
+ },
+ {
+ "name": "Susann Sgorzaly",
+ "homepage": "https://github.com/susgo"
+ },
+ {
+ "name": "Christian Bläul",
+ "homepage": "https://github.com/Fonata"
+ }
+ ],
+ "description": "CSV data parser for PHP",
+ "support": {
+ "issues": "https://github.com/parsecsv/parsecsv-for-php/issues",
+ "source": "https://github.com/parsecsv/parsecsv-for-php"
+ },
+ "install-path": "../parsecsv/php-parsecsv"
+ }
+ ],
+ "dev": true,
+ "dev-package-names": []
+}
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
new file mode 100644
index 0000000..2af8ffc
--- /dev/null
+++ b/vendor/composer/installed.php
@@ -0,0 +1,32 @@
+ array(
+ 'name' => '__root__',
+ 'pretty_version' => 'dev-master',
+ 'version' => 'dev-master',
+ 'reference' => '2a30fbdcdbc8f6f2a59d2d0d87a5d6b9e8549fd6',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'dev' => true,
+ ),
+ 'versions' => array(
+ '__root__' => array(
+ 'pretty_version' => 'dev-master',
+ 'version' => 'dev-master',
+ 'reference' => '2a30fbdcdbc8f6f2a59d2d0d87a5d6b9e8549fd6',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'parsecsv/php-parsecsv' => array(
+ 'pretty_version' => '1.3.2',
+ 'version' => '1.3.2.0',
+ 'reference' => '2d6236cae09133e0533d34ed45ba1e1ecafffebb',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../parsecsv/php-parsecsv',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ ),
+);
diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php
new file mode 100644
index 0000000..103a1ff
--- /dev/null
+++ b/vendor/composer/platform_check.php
@@ -0,0 +1,25 @@
+= 50500)) {
+ $issues[] = 'Your Composer dependencies require a PHP version ">= 5.5.0". You are running ' . PHP_VERSION . '.';
+}
+
+if ($issues) {
+ if (!headers_sent()) {
+ header('HTTP/1.1 500 Internal Server Error');
+ }
+ if (!ini_get('display_errors')) {
+ if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+ fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
+ } elseif (!headers_sent()) {
+ echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
+ }
+ }
+ throw new \RuntimeException(
+ 'Composer detected issues in your platform: ' . implode(' ', $issues)
+ );
+}
diff --git a/vendor/parsecsv/php-parsecsv/.github/workflows/ci.yml b/vendor/parsecsv/php-parsecsv/.github/workflows/ci.yml
new file mode 100644
index 0000000..dfc6dbb
--- /dev/null
+++ b/vendor/parsecsv/php-parsecsv/.github/workflows/ci.yml
@@ -0,0 +1,38 @@
+---
+name: CI
+on:
+ push:
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ php_version:
+ - "7.4"
+ - "7.3"
+ - "7.2"
+ - "7.1"
+ steps:
+ - uses: actions/checkout@v2
+ - uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php_version }}
+ env:
+ COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Get composer cache directory
+ id: composer-cache
+ run: echo "::set-output name=dir::$(composer config cache-files-dir)"
+ - name: Cache composer dependencies
+ uses: actions/cache@v2
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
+ restore-keys: ${{ runner.os }}-composer-
+ - name: Install dependencies
+ run: composer update
+ - name: Validate dependencies
+ run: composer validate
+ - name: Run tests
+ run: vendor/bin/phpunit --configuration tests/phpunit.xml
diff --git a/vendor/parsecsv/php-parsecsv/License.txt b/vendor/parsecsv/php-parsecsv/License.txt
new file mode 100644
index 0000000..84efc1c
--- /dev/null
+++ b/vendor/parsecsv/php-parsecsv/License.txt
@@ -0,0 +1,21 @@
+(The MIT license)
+
+Copyright (c) 2014 Jim Myhrberg.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/parsecsv/php-parsecsv/README.md b/vendor/parsecsv/php-parsecsv/README.md
new file mode 100644
index 0000000..bc47d9f
--- /dev/null
+++ b/vendor/parsecsv/php-parsecsv/README.md
@@ -0,0 +1,246 @@
+# ParseCsv
+[](https://opencollective.com/parsecsv)
+
+ParseCsv is an easy-to-use PHP class that reads and writes CSV data properly. It
+fully conforms to the specifications outlined on the on the
+[Wikipedia article][CSV] (and thus RFC 4180). It has many advanced features which help make your
+life easier when dealing with CSV data.
+
+You may not need a library at all: before using ParseCsv, please make sure if PHP's own `str_getcsv()`, ``fgetcsv()`` or `fputcsv()` meets your needs.
+
+This library was originally created in early 2007 by [jimeh](https://github.com/jimeh) due to the lack of built-in
+and third-party support for handling CSV data in PHP.
+
+[csv]: http://en.wikipedia.org/wiki/Comma-separated_values
+
+## Features
+
+* ParseCsv is a complete and fully featured CSV solution for PHP
+* Supports enclosed values, enclosed commas, double quotes and new lines.
+* Automatic delimiter character detection.
+* Sort data by specific fields/columns.
+* Easy data manipulation.
+* Basic SQL-like _conditions_, _offset_ and _limit_ options for filtering
+ data.
+* Error detection for incorrectly formatted input. It attempts to be
+ intelligent, but can not be trusted 100% due to the structure of CSV, and
+ how different programs like Excel for example outputs CSV data.
+* Support for character encoding conversion using PHP's
+ `iconv()` and `mb_convert_encoding()` functions.
+* Supports PHP 5.5 and higher.
+ It certainly works with PHP 7.2 and all versions in between.
+
+## Installation
+
+Installation is easy using Composer. Just run the following on the
+command line:
+```
+composer require parsecsv/php-parsecsv
+```
+
+If you don't use a framework such as Drupal, Laravel, Symfony, Yii etc.,
+you may have to manually include Composer's autoloader file in your PHP
+script:
+```php
+require_once __DIR__ . '/vendor/autoload.php';
+```
+
+#### Without composer
+Not recommended, but technically possible: you can also clone the
+repository or extract the
+[ZIP](https://github.com/parsecsv/parsecsv-for-php/archive/master.zip).
+To use ParseCSV, you then have to add a `require 'parsecsv.lib.php';` line.
+
+## Example Usage
+
+**Parse a tab-delimited CSV file with encoding conversion**
+
+```php
+$csv = new \ParseCsv\Csv();
+$csv->encoding('UTF-16', 'UTF-8');
+$csv->delimiter = "\t";
+$csv->parseFile('data.tsv');
+print_r($csv->data);
+```
+
+**Auto-detect field delimiter character**
+
+```php
+$csv = new \ParseCsv\Csv();
+$csv->auto('data.csv');
+print_r($csv->data);
+```
+
+**Parse data with offset**
+* ignoring the first X (e.g. two) rows
+```php
+$csv = new \ParseCsv\Csv();
+$csv->offset = 2;
+$csv->parseFile('data.csv');
+print_r($csv->data);
+```
+
+**Limit the number of returned data rows**
+```php
+$csv = new \ParseCsv\Csv();
+$csv->limit = 5;
+$csv->parseFile('data.csv');
+print_r($csv->data);
+```
+
+**Get total number of data rows without parsing whole data**
+* Excluding heading line if present (see $csv->header property)
+```php
+$csv = new \ParseCsv\Csv();
+$csv->loadFile('data.csv');
+$count = $csv->getTotalDataRowCount();
+print_r($count);
+```
+
+**Get most common data type for each column**
+
+```php
+$csv = new \ParseCsv\Csv('data.csv');
+$csv->getDatatypes();
+print_r($csv->data_types);
+```
+
+**Modify data in a CSV file**
+
+Change data values:
+```php
+$csv = new \ParseCsv\Csv();
+$csv->sort_by = 'id';
+$csv->parseFile('data.csv');
+# "4" is the value of the "id" column of the CSV row
+$csv->data[4] = array('firstname' => 'John', 'lastname' => 'Doe', 'email' => 'john@doe.com');
+$csv->save();
+```
+
+Enclose each data value by quotes:
+```php
+$csv = new \ParseCsv\Csv();
+$csv->parseFile('data.csv');
+$csv->enclose_all = true;
+$csv->save();
+```
+
+**Replace field names or set ones if missing**
+
+```php
+$csv = new \ParseCsv\Csv();
+$csv->fields = ['id', 'name', 'category'];
+$csv->parseFile('data.csv');
+```
+
+**Add row/entry to end of CSV file**
+
+_Only recommended when you know the exact structure of the file._
+
+```php
+$csv = new \ParseCsv\Csv();
+$csv->save('data.csv', array(array('1986', 'Home', 'Nowhere', '')), /* append */ true);
+```
+
+**Convert 2D array to CSV data and send headers to browser to treat output as
+a file and download it**
+
+Your web app users would call this an export.
+
+```php
+$csv = new \ParseCsv\Csv();
+$csv->linefeed = "\n";
+$header = array('field 1', 'field 2');
+$csv->output('movies.csv', $data_array, $header, ',');
+```
+
+For more complex examples, see the ``tests`` and `examples` directories.
+
+## Test coverage
+
+All tests are located in the `tests` directory. To execute tests, run the following commands:
+
+````bash
+composer install
+composer run test
+````
+
+When pushing code to GitHub, tests will be executed using GitHub Actions. The relevant configuration is in the
+file `.github/workflows/ci.yml`. To run the `test` action locally, you can execute the following command:
+
+````bash
+make local-ci
+````
+
+## Security
+
+If you discover any security related issues, please email ParseCsv@blaeul.de instead of using GitHub issues.
+
+## Credits
+
+* ParseCsv is based on the concept of [Ming Hong Ng][ming]'s [CsvFileParser][]
+ class.
+
+[ming]: http://minghong.blogspot.com/
+[CsvFileParser]: http://minghong.blogspot.com/2006/07/csv-parser-for-php.html
+
+
+## Contributors
+
+### Code Contributors
+
+This project exists thanks to all the people who contribute.
+
+Please find a complete list on the project's [contributors][] page.
+
+[contributors]: https://github.com/parsecsv/parsecsv-for-php/graphs/contributors
+
+
+### Financial Contributors
+
+Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/parsecsv/contribute)]
+
+#### Individuals
+
+
+
+#### Organizations
+
+Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/parsecsv/contribute)]
+
+
+
+
+
+
+
+
+
+
+
+
+## License
+
+(The MIT license)
+
+Copyright (c) 2014 Jim Myhrberg.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+[](https://travis-ci.org/parsecsv/parsecsv-for-php)
diff --git a/vendor/parsecsv/php-parsecsv/composer.json b/vendor/parsecsv/php-parsecsv/composer.json
new file mode 100644
index 0000000..cbbaca0
--- /dev/null
+++ b/vendor/parsecsv/php-parsecsv/composer.json
@@ -0,0 +1,57 @@
+{
+ "name": "parsecsv/php-parsecsv",
+ "description": "CSV data parser for PHP",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Jim Myhrberg",
+ "email": "contact@jimeh.me"
+ },
+ {
+ "name": "William Knauss",
+ "email": "will.knauss@gmail.com"
+ },
+ {
+ "name": "Susann Sgorzaly",
+ "homepage": "https://github.com/susgo"
+ },
+ {
+ "name": "Christian Bläul",
+ "homepage": "https://github.com/Fonata"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "ParseCsv\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "ParseCsv\\tests\\": "tests"
+ }
+ },
+ "require": {
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6",
+ "squizlabs/php_codesniffer": "^3.5"
+ },
+ "suggest": {
+ "illuminate/support": "Fluent array interface for map functions"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "scripts": {
+ "test": [
+ "vendor/bin/phpunit -c tests tests --disallow-test-output --coverage-clover coverage_clover.xml --whitelist src"
+ ]
+ },
+ "support": {
+ "issues": "https://github.com/parsecsv/parsecsv-for-php/issues",
+ "source": "https://github.com/parsecsv/parsecsv-for-php"
+ }
+}
diff --git a/vendor/parsecsv/php-parsecsv/parsecsv.lib.php b/vendor/parsecsv/php-parsecsv/parsecsv.lib.php
new file mode 100644
index 0000000..265ff22
--- /dev/null
+++ b/vendor/parsecsv/php-parsecsv/parsecsv.lib.php
@@ -0,0 +1,24 @@
+var_name = 'value';
+ */
+
+ /**
+ * Header row:
+ * Use first line/entry as field names
+ *
+ * @var bool
+ */
+ public $heading = true;
+
+ /**
+ * Override field names
+ *
+ * @var array
+ */
+ public $fields = array();
+
+ /**
+ * Sort CSV by this field
+ *
+ * @var string|null
+ */
+ public $sort_by = null;
+
+ /**
+ * Reverse the sort direction
+ *
+ * @var bool
+ */
+ public $sort_reverse = false;
+
+ /**
+ * Sort behavior passed to sort methods
+ *
+ * regular = SORT_REGULAR
+ * numeric = SORT_NUMERIC
+ * string = SORT_STRING
+ *
+ * @var string|null
+ */
+ public $sort_type = SortEnum::SORT_TYPE_REGULAR;
+
+ /**
+ * Field delimiter character
+ *
+ * @var string
+ */
+ public $delimiter = ',';
+
+ /**
+ * Enclosure character
+ *
+ * This is useful for cell values that are either multi-line
+ * or contain the field delimiter character.
+ *
+ * @var string
+ */
+ public $enclosure = '"';
+
+ /**
+ * Force enclosing all columns.
+ *
+ * If false, only cells that are either multi-line or
+ * contain the field delimiter character are enclosed
+ * in the $enclosure char.
+ *
+ * @var bool
+ */
+ public $enclose_all = false;
+
+ /**
+ * Basic SQL-Like conditions for row matching
+ *
+ * @var string|null
+ */
+ public $conditions = null;
+
+ /**
+ * Number of rows to ignore from beginning of data. If present, the heading
+ * row is also counted (if $this->heading == true). In other words,
+ * $offset == 1 and $offset == 0 have the same meaning in that situation.
+ *
+ * @var int|null
+ */
+ public $offset = null;
+
+ /**
+ * Limits the number of returned rows to the specified amount
+ *
+ * @var int|null
+ */
+ public $limit = null;
+
+ /**
+ * Number of rows to analyze when attempting to auto-detect delimiter
+ *
+ * @var int
+ */
+ public $auto_depth = 15;
+
+ /**
+ * Characters that should be ignored when attempting to auto-detect delimiter
+ *
+ * @var string
+ */
+ public $auto_non_chars = "a-zA-Z0-9\n\r";
+
+ /**
+ * preferred delimiter characters, only used when all filtering method
+ * returns multiple possible delimiters (happens very rarely)
+ *
+ * @var string
+ */
+ public $auto_preferred = ",;\t.:|";
+
+ /**
+ * Should we convert the CSV character encoding?
+ * Used for both parse and unparse operations.
+ *
+ * @var bool
+ */
+ public $convert_encoding = false;
+
+ /**
+ * Set the input encoding
+ *
+ * @var string
+ */
+ public $input_encoding = 'ISO-8859-1';
+
+ /**
+ * Set the output encoding
+ *
+ * @var string
+ */
+ public $output_encoding = 'ISO-8859-1';
+
+ /**
+ * Whether to use mb_convert_encoding() instead of iconv().
+ *
+ * The former is platform-independent whereas the latter is the traditional
+ * default go-to solution.
+ *
+ * @var bool (if false, iconv() is used)
+ */
+ public $use_mb_convert_encoding = false;
+
+ /**
+ * Line feed characters used by unparse, save, and output methods
+ * Popular choices are "\r\n" and "\n".
+ *
+ * @var string
+ */
+ public $linefeed = "\r";
+
+ /**
+ * Sets the output delimiter used by the output method
+ *
+ * @var string
+ */
+ public $output_delimiter = ',';
+
+ /**
+ * Sets the output filename
+ *
+ * @var string
+ */
+ public $output_filename = 'data.csv';
+
+ /**
+ * keep raw file data in memory after successful parsing (useful for debugging)
+ *
+ * @var bool
+ */
+ public $keep_file_data = false;
+
+ /**
+ * Internal variables
+ */
+
+ /**
+ * File
+ * Current Filename
+ *
+ * @var string
+ */
+ public $file;
+
+ /**
+ * File Data
+ * Current file data
+ *
+ * @var string
+ */
+ public $file_data;
+
+ /**
+ * Error
+ * Contains the error code if one occurred
+ *
+ * 0 = No errors found. Everything should be fine :)
+ * 1 = Hopefully correctable syntax error was found.
+ * 2 = Enclosure character (double quote by default)
+ * was found in non-enclosed field. This means
+ * the file is either corrupt, or does not
+ * standard CSV formatting. Please validate
+ * the parsed data yourself.
+ *
+ * @var int
+ */
+ public $error = 0;
+
+ /**
+ * Detailed error information
+ *
+ * @var array
+ */
+ public $error_info = array();
+
+ /**
+ * $titles has 4 distinct tasks:
+ * 1. After reading in CSV data, $titles will contain the column headers
+ * present in the data.
+ *
+ * 2. It defines which fields from the $data array to write e.g. when
+ * calling unparse(), and in which order. This lets you skip columns you
+ * don't want in your output, but are present in $data.
+ * See examples/save_to_file_without_header_row.php.
+ *
+ * 3. It lets you rename columns. See StreamTest::testWriteStream for an
+ * example.
+ *
+ * 4. When writing data and $header is true, then $titles is also used for
+ * the first row.
+ *
+ * @var array
+ */
+ public $titles = array();
+
+ /**
+ * Two-dimensional array of CSV data.
+ * The first dimension are the line numbers. Each line is represented as an array with field names as keys.
+ *
+ * @var array
+ */
+ public $data = array();
+
+ use DatatypeTrait;
+
+ /**
+ * Class constructor
+ *
+ * @param string|null $data The CSV string or a direct file path.
+ *
+ * WARNING: Supplying file paths here is
+ * deprecated. Use parseFile() instead.
+ *
+ * @param int|null $offset Number of rows to ignore from the
+ * beginning of the data
+ * @param int|null $limit Limits the number of returned rows
+ * to specified amount
+ * @param string|null $conditions Basic SQL-like conditions for row
+ * matching
+ * @param null|true $keep_file_data Keep raw file data in memory after
+ * successful parsing
+ * (useful for debugging)
+ */
+ public function __construct($data = null, $offset = null, $limit = null, $conditions = null, $keep_file_data = null) {
+ $this->init($offset, $limit, $conditions, $keep_file_data);
+
+ if (!empty($data)) {
+ $this->parse($data);
+ }
+ }
+
+ /**
+ * @param int|null $offset Number of rows to ignore from the
+ * beginning of the data
+ * @param int|null $limit Limits the number of returned rows
+ * to specified amount
+ * @param string|null $conditions Basic SQL-like conditions for row
+ * matching
+ * @param null|true $keep_file_data Keep raw file data in memory after
+ * successful parsing
+ * (useful for debugging)
+ */
+ public function init($offset = null, $limit = null, $conditions = null, $keep_file_data = null) {
+ if (!is_null($offset)) {
+ $this->offset = $offset;
+ }
+
+ if (!is_null($limit)) {
+ $this->limit = $limit;
+ }
+
+ if (!is_null($conditions)) {
+ $this->conditions = $conditions;
+ }
+
+ if (!is_null($keep_file_data)) {
+ $this->keep_file_data = $keep_file_data;
+ }
+ }
+
+ // ==============================================
+ // ----- [ Main Functions ] ---------------------
+ // ==============================================
+
+ /**
+ * Parse a CSV file or string
+ *
+ * @param string|null $dataString The CSV string or a direct file path
+ * WARNING: Supplying file paths here is
+ * deprecated and will trigger an
+ * E_USER_DEPRECATED error.
+ * @param int|null $offset Number of rows to ignore from the
+ * beginning of the data
+ * @param int|null $limit Limits the number of returned rows to
+ * specified amount
+ * @param string|null $conditions Basic SQL-like conditions for row
+ * matching
+ *
+ * @return bool True on success
+ */
+ public function parse($dataString = null, $offset = null, $limit = null, $conditions = null) {
+ if (is_null($dataString)) {
+ $this->data = $this->parseFile();
+ return $this->data !== false;
+ }
+
+ if (empty($dataString)) {
+ return false;
+ }
+
+ $this->init($offset, $limit, $conditions);
+
+ if (strlen($dataString) <= PHP_MAXPATHLEN && is_readable($dataString)) {
+ $this->file = $dataString;
+ $this->data = $this->parseFile();
+ trigger_error(
+ 'Supplying file paths to parse() will no longer ' .
+ 'be supported in a future version of ParseCsv. ' .
+ 'Use ->parseFile() instead.',
+ E_USER_DEPRECATED
+ );
+ } else {
+ $this->file = null;
+ $this->file_data = &$dataString;
+ $this->data = $this->_parse_string();
+ }
+
+ return $this->data !== false;
+ }
+
+ /**
+ * Save changes, or write a new file and/or data.
+ *
+ * @param string $file File location to save to
+ * @param array $data 2D array of data
+ * @param bool $append Append current data to end of target CSV, if file
+ * exists
+ * @param array $fields Field names. Sets the header. If it is not set
+ * $this->titles would be used instead.
+ *
+ * @return bool
+ * True on success
+ */
+ public function save($file = '', $data = array(), $append = FileProcessingModeEnum::MODE_FILE_OVERWRITE, $fields = array()) {
+ if (empty($file)) {
+ $file = &$this->file;
+ }
+
+ $mode = FileProcessingModeEnum::getAppendMode($append);
+ $is_php = preg_match('/\.php$/i', $file) ? true : false;
+
+ return $this->_wfile($file, $this->unparse($data, $fields, $append, $is_php), $mode);
+ }
+
+ /**
+ * Generate a CSV-based string for output.
+ *
+ * Useful for exports in web applications.
+ *
+ * @param string|null $filename If a filename is specified here or in the
+ * object, headers and data will be output
+ * directly to browser as a downloadable
+ * file. This file doesn't have to exist on
+ * the server; the parameter only affects
+ * how the download is called to the
+ * browser.
+ * @param array[] $data 2D array with data
+ * @param array $fields Field names
+ * @param string|null $delimiter character used to separate data
+ *
+ * @return string The resulting CSV string
+ */
+ public function output($filename = null, $data = array(), $fields = array(), $delimiter = null) {
+ if (empty($filename)) {
+ $filename = $this->output_filename;
+ }
+
+ if ($delimiter === null) {
+ $delimiter = $this->output_delimiter;
+ }
+
+ $flat_string = $this->unparse($data, $fields, null, null, $delimiter);
+
+ if (!is_null($filename)) {
+ $mime = $delimiter === "\t" ?
+ 'text/tab-separated-values' :
+ 'application/csv';
+ header('Content-type: ' . $mime);
+ header('Content-Length: ' . strlen($flat_string));
+ header('Cache-Control: no-cache, must-revalidate');
+ header('Pragma: no-cache');
+ header('Expires: 0');
+ header('Content-Disposition: attachment; filename="' . $filename . '"; modification-date="' . date('r') . '";');
+
+ echo $flat_string;
+ }
+
+ return $flat_string;
+ }
+
+ /**
+ * Convert character encoding
+ *
+ * Specify the encoding to use for the next parsing or unparsing.
+ * Calling this function will not change the data held in the object immediately.
+ *
+ * @param string|null $input Input character encoding
+ * If the value null is passed, the existing input encoding remains set (default: ISO-8859-1).
+ * @param string|null $output Output character encoding, uses default if left blank
+ * If the value null is passed, the existing input encoding remains set (default: ISO-8859-1).
+ *
+ * @return void
+ */
+ public function encoding($input = null, $output = null) {
+ $this->convert_encoding = true;
+ if (!is_null($input)) {
+ $this->input_encoding = $input;
+ }
+
+ if (!is_null($output)) {
+ $this->output_encoding = $output;
+ }
+ }
+
+ /**
+ * Auto-detect delimiter: Find delimiter by analyzing a specific number of
+ * rows to determine most probable delimiter character
+ *
+ * @param string|null $file Local CSV file
+ * Supplying CSV data (file content) here is deprecated.
+ * For CSV data, please use autoDetectionForDataString().
+ * Support for CSV data will be removed in v2.0.0.
+ * @param bool $parse True/false parse file directly
+ * @param int|null $search_depth Number of rows to analyze
+ * @param string|null $preferred Preferred delimiter characters
+ * @param string|null $enclosure Enclosure character, default is double quote (").
+ *
+ * @return string|false The detected field delimiter
+ */
+ public function auto($file = null, $parse = true, $search_depth = null, $preferred = null, $enclosure = null) {
+ if (is_null($file)) {
+ $file = $this->file;
+ }
+
+ if (empty($search_depth)) {
+ $search_depth = $this->auto_depth;
+ }
+
+ if (is_null($enclosure)) {
+ $enclosure = $this->enclosure;
+ } else {
+ $this->enclosure = $enclosure;
+ }
+
+ if (is_null($preferred)) {
+ $preferred = $this->auto_preferred;
+ }
+
+ if (empty($this->file_data)) {
+ if ($this->_check_data($file)) {
+ $data = &$this->file_data;
+ } else {
+ return false;
+ }
+ } else {
+ $data = &$this->file_data;
+ }
+
+ $this->autoDetectionForDataString($data, $parse, $search_depth, $preferred, $enclosure);
+
+ return $this->delimiter;
+ }
+
+ public function autoDetectionForDataString($data, $parse = true, $search_depth = null, $preferred = null, $enclosure = null) {
+ $this->file_data = &$data;
+ if (!$this->_detect_and_remove_sep_row_from_data($data)) {
+ $this->_guess_delimiter($search_depth, $preferred, $enclosure, $data);
+ }
+
+ // parse data
+ if ($parse) {
+ $this->data = $this->_parse_string();
+ }
+
+ return $this->delimiter;
+ }
+
+ /**
+ * Get total number of data rows (exclusive heading line if present) in CSV
+ * without parsing the whole data string.
+ *
+ * @return bool|int
+ */
+ public function getTotalDataRowCount() {
+ if (empty($this->file_data)) {
+ return false;
+ }
+
+ $data = $this->file_data;
+
+ $this->_detect_and_remove_sep_row_from_data($data);
+
+ $pattern = sprintf('/%1$s[^%1$s]*%1$s/i', $this->enclosure);
+ preg_match_all($pattern, $data, $matches);
+
+ /** @var array[] $matches */
+ foreach ($matches[0] as $match) {
+ if (empty($match) || (strpos($match, $this->enclosure) === false)) {
+ continue;
+ }
+
+ $replace = str_replace(["\r", "\n"], '', $match);
+ $data = str_replace($match, $replace, $data);
+ }
+
+ $headingRow = $this->heading ? 1 : 0;
+
+ return substr_count($data, "\r")
+ + substr_count($data, "\n")
+ - substr_count($data, "\r\n")
+ - $headingRow;
+ }
+
+ // ==============================================
+ // ----- [ Core Functions ] ---------------------
+ // ==============================================
+
+ /**
+ * Read file to string and call _parse_string()
+ *
+ * @param string|null $file Path to a CSV file.
+ * If configured in files such as php.ini,
+ * the path may also contain a protocol:
+ * https://example.org/some/file.csv
+ *
+ * @return array|false
+ */
+ public function parseFile($file = null) {
+ if (is_null($file)) {
+ $file = $this->file;
+ }
+
+ /**
+ * @see self::keep_file_data
+ * Usually, _parse_string will clean this
+ * Instead of leaving stale data for the next parseFile call behind.
+ */
+ if (empty($this->file_data) && !$this->loadFile($file)) {
+ return false;
+ }
+
+ if (empty($this->file_data)) {
+ return false;
+ }
+ return $this->data = $this->_parse_string();
+ }
+
+ /**
+ * Internal function to parse CSV strings to arrays.
+ *
+ * If you need BOM detection or character encoding conversion, please call
+ * $csv->load_data($your_data_string) first, followed by a call to
+ * $csv->parse($csv->file_data).
+ *
+ * To detect field separators, please use auto() instead.
+ *
+ * @param string|null $data CSV data
+ *
+ * @return array|false
+ * 2D array with CSV data, or false on failure
+ */
+ protected function _parse_string($data = null) {
+ if (empty($data)) {
+ if ($this->_check_data()) {
+ $data = &$this->file_data;
+ } else {
+ return false;
+ }
+ }
+
+ $white_spaces = str_replace($this->delimiter, '', " \t\x0B\0");
+
+ $rows = array();
+ $row = array();
+ $row_count = 0;
+ $current = '';
+ $head = !empty($this->fields) ? $this->fields : array();
+ $col = 0;
+ $enclosed = false;
+ $was_enclosed = false;
+ $strlen = strlen($data);
+
+ // force the parser to process end of data as a character (false) when
+ // data does not end with a line feed or carriage return character.
+ $lch = $data[$strlen - 1];
+ if ($lch != "\n" && $lch != "\r") {
+ $data .= "\n";
+ $strlen++;
+ }
+
+ // walk through each character
+ for ($i = 0; $i < $strlen; $i++) {
+ $ch = isset($data[$i]) ? $data[$i] : false;
+ $nch = isset($data[$i + 1]) ? $data[$i + 1] : false;
+
+ // open/close quotes, and inline quotes
+ if ($ch == $this->enclosure) {
+ if (!$enclosed) {
+ if (ltrim($current, $white_spaces) == '') {
+ $enclosed = true;
+ $was_enclosed = true;
+ } else {
+ $this->error = 2;
+ $error_row = count($rows) + 1;
+ $error_col = $col + 1;
+ $index = $error_row . '-' . $error_col;
+ if (!isset($this->error_info[$index])) {
+ $this->error_info[$index] = array(
+ 'type' => 2,
+ 'info' => 'Syntax error found on row ' . $error_row . '. Non-enclosed fields can not contain double-quotes.',
+ 'row' => $error_row,
+ 'field' => $error_col,
+ 'field_name' => !empty($head[$col]) ? $head[$col] : null,
+ );
+ }
+
+ $current .= $ch;
+ }
+ } elseif ($nch == $this->enclosure) {
+ $current .= $ch;
+ $i++;
+ } elseif ($nch != $this->delimiter && $nch != "\r" && $nch != "\n") {
+ $x = $i + 1;
+ while (isset($data[$x]) && ltrim($data[$x], $white_spaces) == '') {
+ $x++;
+ }
+ if ($data[$x] == $this->delimiter) {
+ $enclosed = false;
+ $i = $x;
+ } else {
+ if ($this->error < 1) {
+ $this->error = 1;
+ }
+
+ $error_row = count($rows) + 1;
+ $error_col = $col + 1;
+ $index = $error_row . '-' . $error_col;
+ if (!isset($this->error_info[$index])) {
+ $this->error_info[$index] = array(
+ 'type' => 1,
+ 'info' =>
+ 'Syntax error found on row ' . (count($rows) + 1) . '. ' .
+ 'A single double-quote was found within an enclosed string. ' .
+ 'Enclosed double-quotes must be escaped with a second double-quote.',
+ 'row' => count($rows) + 1,
+ 'field' => $col + 1,
+ 'field_name' => !empty($head[$col]) ? $head[$col] : null,
+ );
+ }
+
+ $current .= $ch;
+ $enclosed = false;
+ }
+ } else {
+ $enclosed = false;
+ }
+ // end of field/row/csv
+ } elseif ((in_array($ch, [$this->delimiter, "\n", "\r", false], true)) && !$enclosed) {
+ $key = !empty($head[$col]) ? $head[$col] : $col;
+ $row[$key] = $was_enclosed ? $current : trim($current);
+ $current = '';
+ $was_enclosed = false;
+ $col++;
+
+ // end of row
+ if (in_array($ch, ["\n", "\r", false], true)) {
+ if ($this->_validate_offset($row_count) && $this->_validate_row_conditions($row, $this->conditions)) {
+ if ($this->heading && empty($head)) {
+ $head = $row;
+ } elseif (empty($this->fields) || (!empty($this->fields) && (($this->heading && $row_count > 0) || !$this->heading))) {
+ if (!empty($this->sort_by) && !empty($row[$this->sort_by])) {
+ $sort_field = $row[$this->sort_by];
+ if (isset($rows[$sort_field])) {
+ $rows[$sort_field . '_0'] = &$rows[$sort_field];
+ unset($rows[$sort_field]);
+ $sn = 1;
+ while (isset($rows[$sort_field . '_' . $sn])) {
+ $sn++;
+ }
+ $rows[$sort_field . '_' . $sn] = $row;
+ } else {
+ $rows[$sort_field] = $row;
+ }
+
+ } else {
+ $rows[] = $row;
+ }
+ }
+ }
+
+ $row = array();
+ $col = 0;
+ $row_count++;
+
+ if ($this->sort_by === null && $this->limit !== null && count($rows) == $this->limit) {
+ $i = $strlen;
+ }
+
+ if ($ch == "\r" && $nch == "\n") {
+ $i++;
+ }
+ }
+
+ // append character to current field
+ } else {
+ $current .= $ch;
+ }
+ }
+
+ $this->titles = $head;
+ if (!empty($this->sort_by)) {
+ $sort_type = SortEnum::getSorting($this->sort_type);
+ $this->sort_reverse ? krsort($rows, $sort_type) : ksort($rows, $sort_type);
+
+ if ($this->offset !== null || $this->limit !== null) {
+ $rows = array_slice($rows, ($this->offset === null ? 0 : $this->offset), $this->limit, true);
+ }
+ }
+
+ if (!$this->keep_file_data) {
+ $this->file_data = null;
+ }
+
+ return $rows;
+ }
+
+ /**
+ * Create CSV data string from array
+ *
+ * @param array[] $data 2D array with data
+ * @param array $fields field names
+ * @param bool $append if true, field names will not be output
+ * @param bool $is_php if a php die() call should be put on the
+ * first line of the file, this is later
+ * ignored when read.
+ * @param string|null $delimiter field delimiter to use
+ *
+ * @return string CSV data
+ */
+ public function unparse($data = array(), $fields = array(), $append = FileProcessingModeEnum::MODE_FILE_OVERWRITE, $is_php = false, $delimiter = null) {
+ if (!is_array($data) || empty($data)) {
+ $data = &$this->data;
+ } else {
+ /** @noinspection ReferenceMismatchInspection */
+ $this->data = $data;
+ }
+
+ if (!is_array($fields) || empty($fields)) {
+ $fields = &$this->titles;
+ }
+
+ if ($delimiter === null) {
+ $delimiter = $this->delimiter;
+ }
+
+ $string = $is_php ? "" . $this->linefeed : '';
+ $entry = array();
+
+ // create heading
+ /** @noinspection ReferenceMismatchInspection */
+ $fieldOrder = $this->_validate_fields_for_unparse($fields);
+ if (!$fieldOrder && !empty($data)) {
+ $column_count = count($data[0]);
+ $columns = range(0, $column_count - 1, 1);
+ $fieldOrder = array_combine($columns, $columns);
+ }
+
+ if ($this->heading && !$append && !empty($fields)) {
+ foreach ($fieldOrder as $column_name) {
+ $entry[] = $this->_enclose_value($column_name, $delimiter);
+ }
+
+ $string .= implode($delimiter, $entry) . $this->linefeed;
+ $entry = array();
+ }
+
+ // create data
+ foreach ($data as $key => $row) {
+ foreach (array_keys($fieldOrder) as $index) {
+ $cell_value = $row[$index];
+ $entry[] = $this->_enclose_value($cell_value, $delimiter);
+ }
+
+ $string .= implode($delimiter, $entry) . $this->linefeed;
+ $entry = array();
+ }
+
+ if ($this->convert_encoding) {
+ /** @noinspection PhpComposerExtensionStubsInspection
+ *
+ * If you receive an error at the following 3 lines, you must enable
+ * the following PHP extension:
+ *
+ * - if $use_mb_convert_encoding is true: mbstring
+ * - if $use_mb_convert_encoding is false: iconv
+ */
+ $string = $this->use_mb_convert_encoding ?
+ mb_convert_encoding($string, $this->output_encoding, $this->input_encoding) :
+ iconv($this->input_encoding, $this->output_encoding, $string);
+ }
+
+ return $string;
+ }
+
+ /**
+ * @param array $fields
+ *
+ * @return array|false
+ */
+ private function _validate_fields_for_unparse(array $fields) {
+ if (empty($fields)) {
+ $fields = $this->titles;
+ }
+
+ if (empty($fields)) {
+ return array();
+ }
+
+ // this is needed because sometime titles property is overwritten instead of using fields parameter!
+ $titlesOnParse = !empty($this->data) ? array_keys(reset($this->data)) : array();
+
+ // both are identical, also in ordering OR we have no data (only titles)
+ if (empty($titlesOnParse) || array_values($fields) === array_values($titlesOnParse)) {
+ return array_combine($fields, $fields);
+ }
+
+ // if renaming given by: $oldName => $newName (maybe with reorder and / or subset):
+ // todo: this will only work if titles are unique
+ $fieldOrder = array_intersect(array_flip($fields), $titlesOnParse);
+ if (!empty($fieldOrder)) {
+ return array_flip($fieldOrder);
+ }
+
+ $fieldOrder = array_intersect($fields, $titlesOnParse);
+ if (!empty($fieldOrder)) {
+ return array_combine($fieldOrder, $fieldOrder);
+ }
+
+ // original titles are not given in fields. that is okay if count is okay.
+ if (count($fields) != count($titlesOnParse)) {
+ throw new \UnexpectedValueException(
+ "The specified fields do not match any titles and do not match column count.\n" .
+ "\$fields was " . print_r($fields, true) .
+ "\$titlesOnParse was " . print_r($titlesOnParse, true));
+ }
+
+ return array_combine($titlesOnParse, $fields);
+ }
+
+ /**
+ * Load local file or string.
+ *
+ * Only use this function if auto() and parse() don't handle your data well.
+ *
+ * This function load_data() is able to handle BOMs and encodings. The data
+ * is stored within the $this->file_data class field.
+ *
+ * @param string|null $input CSV file path or CSV data as a string
+ *
+ * Supplying CSV data (file content) here is deprecated.
+ * For CSV data, please use loadDataString().
+ * Support for CSV data will be removed in v2.0.0.
+ *
+ * @return bool True on success
+ * @deprecated Use loadDataString() or loadFile() instead.
+ */
+ public function load_data($input = null) {
+ return $this->loadFile($input);
+ }
+
+ /**
+ * Load a file, but don't parse it.
+ *
+ * Only use this function if auto() and parseFile() don't handle your data well.
+ *
+ * This function is able to handle BOMs and encodings. The data
+ * is stored within the $this->file_data class field.
+ *
+ * @param string|null $file CSV file path
+ *
+ * @return bool True on success
+ */
+ public function loadFile($file = null) {
+ $data = null;
+
+ if (is_null($file)) {
+ $data = $this->_rfile($this->file);
+ } elseif (\strlen($file) <= PHP_MAXPATHLEN && file_exists($file)) {
+ $data = $this->_rfile($file);
+ if ($this->file != $file) {
+ $this->file = $file;
+ }
+ } else {
+ // It is CSV data as a string.
+
+ // WARNING:
+ // Supplying CSV data to load_data() will no longer
+ // be supported in a future version of ParseCsv.
+ // This function will return false for invalid paths from v2.0.0 onwards.
+
+ // Use ->loadDataString() instead.
+
+ $data = $file;
+ }
+
+ return $this->loadDataString($data);
+ }
+
+ /**
+ * Load a data string, but don't parse it.
+ *
+ * Only use this function if autoDetectionForDataString() and parse() don't handle your data well.
+ *
+ * This function is able to handle BOMs and encodings. The data
+ * is stored within the $this->file_data class field.
+ *
+ * @param string|null $file_path CSV file path
+ *
+ * @return bool True on success
+ */
+ public function loadDataString($data) {
+ if (!empty($data)) {
+ if (strpos($data, "\xef\xbb\xbf") === 0) {
+ // strip off BOM (UTF-8)
+ $data = substr($data, 3);
+ $this->encoding('UTF-8');
+ } elseif (strpos($data, "\xff\xfe") === 0) {
+ // strip off BOM (UTF-16 little endian)
+ $data = substr($data, 2);
+ $this->encoding("UCS-2LE");
+ } elseif (strpos($data, "\xfe\xff") === 0) {
+ // strip off BOM (UTF-16 big endian)
+ $data = substr($data, 2);
+ $this->encoding("UTF-16");
+ }
+
+ if ($this->convert_encoding && $this->input_encoding !== $this->output_encoding) {
+ /** @noinspection PhpComposerExtensionStubsInspection
+ *
+ * If you receive an error at the following 3 lines, you must enable
+ * the following PHP extension:
+ *
+ * - if $use_mb_convert_encoding is true: mbstring
+ * - if $use_mb_convert_encoding is false: iconv
+ */
+ $data = $this->use_mb_convert_encoding ?
+ mb_convert_encoding($data, $this->output_encoding, $this->input_encoding) :
+ iconv($this->input_encoding, $this->output_encoding, $data);
+ }
+
+ if (substr($data, -1) != "\n") {
+ $data .= "\n";
+ }
+
+ $this->file_data = &$data;
+ return true;
+ }
+
+ return false;
+ }
+
+ // ==============================================
+ // ----- [ Internal Functions ] -----------------
+ // ==============================================
+
+ /**
+ * Validate a row against specified conditions
+ *
+ * @param array $row array with values from a row
+ * @param string|null $conditions specified conditions that the row must match
+ *
+ * @return bool
+ */
+ protected function _validate_row_conditions($row = array(), $conditions = null) {
+ if (!empty($row)) {
+ if (!empty($conditions)) {
+ $condition_array = (strpos($conditions, ' OR ') !== false) ?
+ explode(' OR ', $conditions) :
+ array($conditions);
+ $or = '';
+ foreach ($condition_array as $key => $value) {
+ if (strpos($value, ' AND ') !== false) {
+ $value = explode(' AND ', $value);
+ $and = '';
+
+ foreach ($value as $k => $v) {
+ $and .= $this->_validate_row_condition($row, $v);
+ }
+
+ $or .= (strpos($and, '0') !== false) ? '0' : '1';
+ } else {
+ $or .= $this->_validate_row_condition($row, $value);
+ }
+ }
+
+ return strpos($or, '1') !== false;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Validate a row against a single condition
+ *
+ * @param array $row array with values from a row
+ * @param string $condition specified condition that the row must match
+ *
+ * @return string single 0 or 1
+ */
+ protected function _validate_row_condition($row, $condition) {
+ $operators = array(
+ '=',
+ 'equals',
+ 'is',
+ '!=',
+ 'is not',
+ '<',
+ 'is less than',
+ '>',
+ 'is greater than',
+ '<=',
+ 'is less than or equals',
+ '>=',
+ 'is greater than or equals',
+ 'contains',
+ 'does not contain',
+ );
+
+ $operators_regex = array();
+
+ foreach ($operators as $value) {
+ $operators_regex[] = preg_quote($value, '/');
+ }
+
+ $operators_regex = implode('|', $operators_regex);
+
+ if (preg_match('/^(.+) (' . $operators_regex . ') (.+)$/i', trim($condition), $capture)) {
+ $field = $capture[1];
+ $op = strtolower($capture[2]);
+ $value = $capture[3];
+ if ($op == 'equals' && preg_match('/^(.+) is (less|greater) than or$/i', $field, $m)) {
+ $field = $m[1];
+ $op = strtolower($m[2]) == 'less' ? '<=' : '>=';
+ }
+ if ($op == 'is' && preg_match('/^(less|greater) than (.+)$/i', $value, $m)) {
+ $value = $m[2];
+ $op = strtolower($m[1]) == 'less' ? '<' : '>';
+ }
+ if ($op == 'is' && preg_match('/^not (.+)$/i', $value, $m)) {
+ $value = $m[1];
+ $op = '!=';
+ }
+
+ if (preg_match('/^([\'"])(.*)([\'"])$/', $value, $capture) && $capture[1] == $capture[3]) {
+ $value = strtr($capture[2], array(
+ "\\n" => "\n",
+ "\\r" => "\r",
+ "\\t" => "\t",
+ ));
+
+ $value = stripslashes($value);
+ }
+
+ if (array_key_exists($field, $row)) {
+ $op_equals = in_array($op, ['=', 'equals', 'is'], true);
+ if ($op_equals && $row[$field] == $value) {
+ return '1';
+ } elseif (($op == '!=' || $op == 'is not') && $row[$field] != $value) {
+ return '1';
+ } elseif (($op == '<' || $op == 'is less than') && $row[$field] < $value) {
+ return '1';
+ } elseif (($op == '>' || $op == 'is greater than') && $row[$field] > $value) {
+ return '1';
+ } elseif (($op == '<=' || $op == 'is less than or equals') && $row[$field] <= $value) {
+ return '1';
+ } elseif (($op == '>=' || $op == 'is greater than or equals') && $row[$field] >= $value) {
+ return '1';
+ } elseif ($op == 'contains' && preg_match('/' . preg_quote($value, '/') . '/i', $row[$field])) {
+ return '1';
+ } elseif ($op == 'does not contain' && !preg_match('/' . preg_quote($value, '/') . '/i', $row[$field])) {
+ return '1';
+ } else {
+ return '0';
+ }
+ }
+ }
+
+ return '1';
+ }
+
+ /**
+ * Validates if the row is within the offset or not if sorting is disabled
+ *
+ * @param int $current_row the current row number being processed
+ *
+ * @return bool
+ */
+ protected function _validate_offset($current_row) {
+ return
+ $this->sort_by !== null ||
+ $this->offset === null ||
+ $current_row >= $this->offset ||
+ ($this->heading && $current_row == 0);
+ }
+
+ /**
+ * Enclose values if needed
+ * - only used by unparse()
+ *
+ * @param string|null $value Cell value to process
+ * @param string $delimiter Character to put between cells on the same row
+ *
+ * @return string Processed value
+ */
+ protected function _enclose_value($value, $delimiter) {
+ if ($value !== null && $value != '') {
+ $delimiter_quoted = $delimiter ?
+ preg_quote($delimiter, '/') . "|"
+ : '';
+ $enclosure_quoted = preg_quote($this->enclosure, '/');
+ $pattern = "/" . $delimiter_quoted . $enclosure_quoted . "|\n|\r/i";
+ if ($this->enclose_all || preg_match($pattern, $value) || strpos($value, ' ') === 0 || substr($value, -1) == ' ') {
+ $value = str_replace($this->enclosure, $this->enclosure . $this->enclosure, $value);
+ $value = $this->enclosure . $value . $this->enclosure;
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Check file data
+ *
+ * @param string|null $file local filename
+ *
+ * @return bool
+ */
+ protected function _check_data($file = null) {
+ if (empty($this->file_data)) {
+ if (is_null($file)) {
+ $file = $this->file;
+ }
+
+ return $this->loadFile($file);
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if passed info might be delimiter.
+ * Only used by find_delimiter
+ *
+ * @param string $char Potential field separating character
+ * @param array $array Frequency
+ * @param int $depth Number of analyzed rows
+ * @param string $preferred Preferred delimiter characters
+ *
+ * @return string|false special string used for delimiter selection, or false
+ */
+ protected function _check_count($char, $array, $depth, $preferred) {
+ if ($depth === count($array)) {
+ $first = null;
+ $equal = null;
+ $almost = false;
+ foreach ($array as $value) {
+ if ($first == null) {
+ $first = $value;
+ } elseif ($value == $first && $equal !== false) {
+ $equal = true;
+ } elseif ($value == $first + 1 && $equal !== false) {
+ $equal = true;
+ $almost = true;
+ } else {
+ $equal = false;
+ }
+ }
+
+ if ($equal || $depth === 1) {
+ $match = $almost ? 2 : 1;
+ $pref = strpos($preferred, $char);
+ $pref = ($pref !== false) ? str_pad($pref, 3, '0', STR_PAD_LEFT) : '999';
+
+ return $pref . $match . '.' . (99999 - str_pad($first, 5, '0', STR_PAD_LEFT));
+ } else {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Read local file.
+ *
+ * @param string $filePath local filename
+ *
+ * @return string|false Data from file, or false on failure
+ */
+ protected function _rfile($filePath) {
+ if (is_readable($filePath)) {
+ $data = file_get_contents($filePath);
+ if ($data === false) {
+ return false;
+ }
+
+ if (preg_match('/\.php$/i', $filePath) && preg_match('/<\?.*?\?>(.*)/ms', $data, $strip)) {
+ // Return section behind closing tags.
+ // This parsing is deprecated and will be removed in v2.0.0.
+ $data = ltrim($strip[1]);
+ }
+
+ return rtrim($data, "\r\n");
+ }
+
+ return false;
+ }
+
+ /**
+ * Write to local file
+ *
+ * @param string $file local filename
+ * @param string $content data to write to file
+ * @param string $mode fopen() mode
+ * @param int $lock flock() mode
+ *
+ * @return bool
+ * True on success
+ *
+ */
+ protected function _wfile($file, $content = '', $mode = 'wb', $lock = LOCK_EX) {
+ if ($fp = fopen($file, $mode)) {
+ flock($fp, $lock);
+ $re = fwrite($fp, $content);
+ $re2 = fclose($fp);
+
+ if ($re !== false && $re2 !== false) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Detect separator using a nonstandard hack: such file starts with the
+ * first line containing only "sep=;", where the last character is the
+ * separator. Microsoft Excel is able to open such files.
+ *
+ * @param string $data file data
+ *
+ * @return string|false detected delimiter, or false if none found
+ */
+ protected function _get_delimiter_from_sep_row($data) {
+ $sep = false;
+ // 32 bytes should be quite enough data for our sniffing, chosen arbitrarily
+ $sepPrefix = substr($data, 0, 32);
+ if (preg_match('/^sep=(.)\\r?\\n/i', $sepPrefix, $sepMatch)) {
+ // we get separator.
+ $sep = $sepMatch[1];
+ }
+ return $sep;
+ }
+
+ /**
+ * Support for Excel-compatible sep=? row.
+ *
+ * @param string $data_string file data to be updated
+ *
+ * @return bool TRUE if sep= line was found at the very beginning of the file
+ */
+ protected function _detect_and_remove_sep_row_from_data(&$data_string) {
+ $sep = $this->_get_delimiter_from_sep_row($data_string);
+ if ($sep === false) {
+ return false;
+ }
+
+ $this->delimiter = $sep;
+
+ // likely to be 5, but let's not assume we're always single-byte.
+ $pos = 4 + strlen($sep);
+ // the next characters should be a line-end
+ if (substr($data_string, $pos, 1) === "\r") {
+ $pos++;
+ }
+ if (substr($data_string, $pos, 1) === "\n") {
+ $pos++;
+ }
+
+ // remove delimiter and its line-end (the data param is by-ref!)
+ $data_string = substr($data_string, $pos);
+ return true;
+ }
+
+ /**
+ * @param int $search_depth Number of rows to analyze
+ * @param string $preferred Preferred delimiter characters
+ * @param string $enclosure Enclosure character, default is double quote
+ * @param string $data The file content
+ */
+ protected function _guess_delimiter($search_depth, $preferred, $enclosure, $data) {
+ $chars = [];
+ $strlen = strlen($data);
+ $enclosed = false;
+ $current_row = 1;
+ $to_end = true;
+
+ // The dash is the only character we don't want quoted, as it would
+ // prevent character ranges within $auto_non_chars:
+ $quoted_auto_non_chars = preg_quote($this->auto_non_chars, '/');
+ $quoted_auto_non_chars = str_replace('\-', '-', $quoted_auto_non_chars);
+ $pattern = '/[' . $quoted_auto_non_chars . ']/i';
+
+ // walk specific depth finding possible delimiter characters
+ for ($i = 0; $i < $strlen; $i++) {
+ $ch = $data[$i];
+ $nch = isset($data[$i + 1]) ? $data[$i + 1] : false;
+ $pch = isset($data[$i - 1]) ? $data[$i - 1] : false;
+
+ // open and closing quotes
+ $is_newline = ($ch == "\n" && $pch != "\r") || $ch == "\r";
+ if ($ch == $enclosure) {
+ if (!$enclosed || $nch != $enclosure) {
+ $enclosed = !$enclosed;
+ } elseif ($enclosed) {
+ $i++;
+ }
+
+ // end of row
+ } elseif ($is_newline && !$enclosed) {
+ if ($current_row >= $search_depth) {
+ $strlen = 0;
+ $to_end = false;
+ } else {
+ $current_row++;
+ }
+
+ // count character
+ } elseif (!$enclosed) {
+ if (!preg_match($pattern, $ch)) {
+ if (!isset($chars[$ch][$current_row])) {
+ $chars[$ch][$current_row] = 1;
+ } else {
+ $chars[$ch][$current_row]++;
+ }
+ }
+ }
+ }
+
+ // filtering
+ $depth = $to_end ? $current_row - 1 : $current_row;
+ $filtered = [];
+ foreach ($chars as $char => $value) {
+ if ($match = $this->_check_count($char, $value, $depth, $preferred)) {
+ $filtered[$match] = $char;
+ }
+ }
+
+ // capture most probable delimiter
+ ksort($filtered);
+ $this->delimiter = reset($filtered);
+ }
+
+ /**
+ * getCollection
+ * Returns a Illuminate/Collection object
+ * This may prove to be helpful to people who want to
+ * create macros, and or use map functions
+ *
+ * @access public
+ * @link https://laravel.com/docs/5.6/collections
+ *
+ * @throws \ErrorException - If the Illuminate\Support\Collection class is not found
+ *
+ * @return Collection
+ */
+ public function getCollection() {
+ //does the Illuminate\Support\Collection class exists?
+ //this uses the autoloader to try to determine
+ //@see http://php.net/manual/en/function.class-exists.php
+ if (class_exists('Illuminate\Support\Collection', true) == false) {
+ throw new \ErrorException('It would appear you have not installed the illuminate/support package!');
+ }
+
+ //return the collection
+ return new Collection($this->data);
+ }
+}
diff --git a/vendor/parsecsv/php-parsecsv/src/enums/AbstractEnum.php b/vendor/parsecsv/php-parsecsv/src/enums/AbstractEnum.php
new file mode 100644
index 0000000..19dae3f
--- /dev/null
+++ b/vendor/parsecsv/php-parsecsv/src/enums/AbstractEnum.php
@@ -0,0 +1,38 @@
+isValid($value)) {
+ throw new \UnexpectedValueException("Value '$value' is not part of the enum " . get_called_class());
+ }
+ $this->value = $value;
+ }
+
+ public static function getConstants() {
+ $class = get_called_class();
+ $reflection = new \ReflectionClass($class);
+
+ return $reflection->getConstants();
+ }
+
+ /**
+ * Check if enum value is valid
+ *
+ * @param $value
+ *
+ * @return bool
+ */
+ public static function isValid($value) {
+ return in_array($value, static::getConstants(), true);
+ }
+}
diff --git a/vendor/parsecsv/php-parsecsv/src/enums/DatatypeEnum.php b/vendor/parsecsv/php-parsecsv/src/enums/DatatypeEnum.php
new file mode 100644
index 0000000..7f490e9
--- /dev/null
+++ b/vendor/parsecsv/php-parsecsv/src/enums/DatatypeEnum.php
@@ -0,0 +1,120 @@
+ null,
+ self::TYPE_INT => 'isValidInteger',
+ self::TYPE_BOOL => 'isValidBoolean',
+ self::TYPE_FLOAT => 'isValidFloat',
+ self::TYPE_DATE => 'isValidDate',
+ );
+
+ /**
+ * Checks data type for given string.
+ *
+ * @param string $value
+ *
+ * @return bool|string
+ */
+ public static function getValidTypeFromSample($value) {
+ $value = trim((string) $value);
+
+ if (empty($value)) {
+ return false;
+ }
+
+ foreach (self::$validators as $type => $validator) {
+ if ($validator === null) {
+ continue;
+ }
+
+ if (method_exists(__CLASS__, $validator) && self::$validator($value)) {
+ return $type;
+ }
+ }
+
+ return self::__DEFAULT;
+ }
+
+ /**
+ * Check if string is float value.
+ *
+ * @param string $value
+ *
+ * @return bool
+ */
+ private static function isValidFloat($value) {
+ return (bool) preg_match(self::REGEX_FLOAT, $value);
+ }
+
+ /**
+ * Check if string is integer value.
+ *
+ * @param string $value
+ *
+ * @return bool
+ */
+ private static function isValidInteger($value) {
+ return (bool) preg_match(self::REGEX_INT, $value);
+ }
+
+ /**
+ * Check if string is boolean.
+ *
+ * @param string $value
+ *
+ * @return bool
+ */
+ private static function isValidBoolean($value) {
+ return (bool) preg_match(self::REGEX_BOOL, $value);
+ }
+
+ /**
+ * Check if string is date.
+ *
+ * @param string $value
+ *
+ * @return bool
+ */
+ private static function isValidDate($value) {
+ return (bool) strtotime($value);
+ }
+}
diff --git a/vendor/parsecsv/php-parsecsv/src/enums/FileProcessingModeEnum.php b/vendor/parsecsv/php-parsecsv/src/enums/FileProcessingModeEnum.php
new file mode 100644
index 0000000..b545c68
--- /dev/null
+++ b/vendor/parsecsv/php-parsecsv/src/enums/FileProcessingModeEnum.php
@@ -0,0 +1,28 @@
+ SORT_REGULAR,
+ self::SORT_TYPE_STRING => SORT_STRING,
+ self::SORT_TYPE_NUMERIC => SORT_NUMERIC,
+ );
+
+ public static function getSorting($type) {
+ if (array_key_exists($type, self::$sorting)) {
+ return self::$sorting[$type];
+ }
+
+ return self::$sorting[self::__DEFAULT];
+ }
+}
diff --git a/vendor/parsecsv/php-parsecsv/src/extensions/DatatypeTrait.php b/vendor/parsecsv/php-parsecsv/src/extensions/DatatypeTrait.php
new file mode 100644
index 0000000..c0a4c10
--- /dev/null
+++ b/vendor/parsecsv/php-parsecsv/src/extensions/DatatypeTrait.php
@@ -0,0 +1,102 @@
+= 5.5
+ *
+ * @uses DatatypeEnum::getValidTypeFromSample
+ *
+ * @return array|bool
+ */
+ public function getDatatypes() {
+ if (empty($this->data)) {
+ $this->data = $this->_parse_string();
+ }
+ if (!is_array($this->data)) {
+ throw new \UnexpectedValueException('No data set yet.');
+ }
+
+ $result = [];
+ foreach ($this->titles as $cName) {
+ $column = array_column($this->data, $cName);
+ $cDatatypes = array_map(DatatypeEnum::class . '::getValidTypeFromSample', $column);
+
+ $result[$cName] = $this->getMostFrequentDatatypeForColumn($cDatatypes);
+ }
+
+ $this->data_types = $result;
+
+ return !empty($this->data_types) ? $this->data_types : [];
+ }
+
+ /**
+ * Check data type of titles / first row for auto detecting if this could be
+ * a heading line.
+ *
+ * Requires PHP >= 5.5
+ *
+ * @uses DatatypeEnum::getValidTypeFromSample
+ *
+ * @return bool
+ */
+ public function autoDetectFileHasHeading() {
+ if (empty($this->data)) {
+ throw new \UnexpectedValueException('No data set yet.');
+ }
+
+ if ($this->heading) {
+ $firstRow = $this->titles;
+ } else {
+ $firstRow = $this->data[0];
+ }
+
+ $firstRow = array_filter($firstRow);
+ if (empty($firstRow)) {
+ return false;
+ }
+
+ $firstRowDatatype = array_map(DatatypeEnum::class . '::getValidTypeFromSample', $firstRow);
+
+ return $this->getMostFrequentDatatypeForColumn($firstRowDatatype) === DatatypeEnum::TYPE_STRING;
+ }
+}