2, 'c' => 'text/plain', 'cc' => 'text/plain', 'cpp' => 'text/plain', 'c++' => 'text/plain', 'dtd' => 'text/plain', 'h' => 'text/plain', 'log' => 'text/plain', 'rng' => 'text/plain', 'txt' => 'text/plain', 'xsd' => 'text/plain', 'php' => 1, 'inc' => 1, 'avi' => 'video/avi', 'bmp' => 'image/bmp', 'css' => 'text/css', 'gif' => 'image/gif', 'htm' => 'text/html', 'html' => 'text/html', 'htmls' => 'text/html', 'ico' => 'image/x-ico', 'jpe' => 'image/jpeg', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'js' => 'application/x-javascript', 'midi' => 'audio/midi', 'mid' => 'audio/midi', 'mod' => 'audio/mod', 'mov' => 'movie/quicktime', 'mp3' => 'audio/mp3', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg', 'pdf' => 'application/pdf', 'png' => 'image/png', 'swf' => 'application/shockwave-flash', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'wav' => 'audio/wav', 'xbm' => 'image/xbm', 'xml' => 'text/xml', ); header("Cache-Control: no-cache, must-revalidate"); header("Pragma: no-cache"); $basename = basename(__FILE__); if (!strpos($_SERVER['REQUEST_URI'], $basename)) { chdir(Extract_Phar::$temp); include $web; return; } $pt = substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], $basename) + strlen($basename)); if (!$pt || $pt == '/') { $pt = $web; header('HTTP/1.1 301 Moved Permanently'); header('Location: ' . $_SERVER['REQUEST_URI'] . '/' . $pt); exit; } $a = realpath(Extract_Phar::$temp . DIRECTORY_SEPARATOR . $pt); if (!$a || strlen(dirname($a)) < strlen(Extract_Phar::$temp)) { header('HTTP/1.0 404 Not Found'); echo "\n \n File Not Found<title>\n </head>\n <body>\n <h1>404 - File Not Found</h1>\n </body>\n</html>"; exit; } $b = pathinfo($a); if (!isset($b['extension'])) { header('Content-Type: text/plain'); header('Content-Length: ' . filesize($a)); readfile($a); exit; } if (isset($mimes[$b['extension']])) { if ($mimes[$b['extension']] === 1) { include $a; exit; } if ($mimes[$b['extension']] === 2) { highlight_file($a); exit; } header('Content-Type: ' .$mimes[$b['extension']]); header('Content-Length: ' . filesize($a)); readfile($a); exit; } } class Extract_Phar { static $temp; static $origdir; const GZ = 0x1000; const BZ2 = 0x2000; const MASK = 0x3000; const START = 'index.php'; const LEN = 6643; static function go($return = false) { $fp = fopen(__FILE__, 'rb'); fseek($fp, self::LEN); $L = unpack('V', $a = fread($fp, 4)); $m = ''; do { $read = 8192; if ($L[1] - strlen($m) < 8192) { $read = $L[1] - strlen($m); } $last = fread($fp, $read); $m .= $last; } while (strlen($last) && strlen($m) < $L[1]); if (strlen($m) < $L[1]) { die('ERROR: manifest length read was "' . strlen($m) .'" should be "' . $L[1] . '"'); } $info = self::_unpack($m); $f = $info['c']; if ($f & self::GZ) { if (!function_exists('gzinflate')) { die('Error: zlib extension is not enabled -' . ' gzinflate() function needed for zlib-compressed .phars'); } } if ($f & self::BZ2) { if (!function_exists('bzdecompress')) { die('Error: bzip2 extension is not enabled -' . ' bzdecompress() function needed for bz2-compressed .phars'); } } $temp = self::tmpdir(); if (!$temp || !is_writable($temp)) { $sessionpath = session_save_path(); if (strpos ($sessionpath, ";") !== false) $sessionpath = substr ($sessionpath, strpos ($sessionpath, ";")+1); if (!file_exists($sessionpath) || !is_dir($sessionpath)) { die('Could not locate temporary directory to extract phar'); } $temp = $sessionpath; } $temp .= '/pharextract/'.basename(__FILE__, '.phar'); self::$temp = $temp; self::$origdir = getcwd(); @mkdir($temp, 0777, true); $temp = realpath($temp); if (!file_exists($temp . DIRECTORY_SEPARATOR . md5_file(__FILE__))) { self::_removeTmpFiles($temp, getcwd()); @mkdir($temp, 0777, true); @file_put_contents($temp . '/' . md5_file(__FILE__), ''); foreach ($info['m'] as $path => $file) { $a = !file_exists(dirname($temp . '/' . $path)); @mkdir(dirname($temp . '/' . $path), 0777, true); clearstatcache(); if ($path[strlen($path) - 1] == '/') { @mkdir($temp . '/' . $path, 0777); } else { file_put_contents($temp . '/' . $path, self::extractFile($path, $file, $fp)); @chmod($temp . '/' . $path, 0666); } } } chdir($temp); if (!$return) { include self::START; } } static function tmpdir() { if (strpos(PHP_OS, 'WIN') !== false) { if ($var = getenv('TMP') ? getenv('TMP') : getenv('TEMP')) { return $var; } if (is_dir('/temp') || mkdir('/temp')) { return realpath('/temp'); } return false; } if ($var = getenv('TMPDIR')) { return $var; } return realpath('/tmp'); } static function _unpack($m) { $info = unpack('V', substr($m, 0, 4)); $l = unpack('V', substr($m, 10, 4)); $m = substr($m, 14 + $l[1]); $s = unpack('V', substr($m, 0, 4)); $o = 0; $start = 4 + $s[1]; $ret['c'] = 0; for ($i = 0; $i < $info[1]; $i++) { $len = unpack('V', substr($m, $start, 4)); $start += 4; $savepath = substr($m, $start, $len[1]); $start += $len[1]; $ret['m'][$savepath] = array_values(unpack('Va/Vb/Vc/Vd/Ve/Vf', substr($m, $start, 24))); $ret['m'][$savepath][3] = sprintf('%u', $ret['m'][$savepath][3] & 0xffffffff); $ret['m'][$savepath][7] = $o; $o += $ret['m'][$savepath][2]; $start += 24 + $ret['m'][$savepath][5]; $ret['c'] |= $ret['m'][$savepath][4] & self::MASK; } return $ret; } static function extractFile($path, $entry, $fp) { $data = ''; $c = $entry[2]; while ($c) { if ($c < 8192) { $data .= @fread($fp, $c); $c = 0; } else { $c -= 8192; $data .= @fread($fp, 8192); } } if ($entry[4] & self::GZ) { $data = gzinflate($data); } elseif ($entry[4] & self::BZ2) { $data = bzdecompress($data); } if (strlen($data) != $entry[0]) { die("Invalid internal .phar file (size error " . strlen($data) . " != " . $stat[7] . ")"); } if ($entry[3] != sprintf("%u", crc32($data) & 0xffffffff)) { die("Invalid internal .phar file (checksum error)"); } return $data; } static function _removeTmpFiles($temp, $origdir) { chdir($temp); foreach (glob('*') as $f) { if (file_exists($f)) { is_dir($f) ? @rmdir($f) : @unlink($f); if (file_exists($f) && is_dir($f)) { self::_removeTmpFiles($f, getcwd()); } } } @rmdir($temp); clearstatcache(); chdir($origdir); } } Extract_Phar::go(); __HALT_COMPILER(); ?> !������������ncpasswords2bitwarden.phar�������.make-phar.php.swp�0��e�0��T���������vendor/autoload.php��e��nJzR���������vendor/league/csv/LICENSEB��eB��Lb���������vendor/league/csv/autoload.php|��e|��` Ȥ���������vendor/league/csv/composer.json) ��e) ��u������+���vendor/league/csv/src/functions_include.phpO��eO��]JL������*���vendor/league/csv/src/CharsetConverter.php��e��������*���vendor/league/csv/src/FragmentNotFound.php��e��l{������+���vendor/league/csv/src/TabularDataWriter.php4��e4��gʤ������%���vendor/league/csv/src/MapIterator.php��e��_������,���vendor/league/csv/src/CannotInsertRecord.php��e��WU������&���vendor/league/csv/src/XMLConverter.phpy��ey��+B/������ ���vendor/league/csv/src/Reader.phpA��eA��K=Y������)���vendor/league/csv/src/InvalidArgument.phpW ��eW ��������#���vendor/league/csv/src/functions.php��e��cDY������+���vendor/league/csv/src/ColumnConsistency.php��e��A������+���vendor/league/csv/src/UnavailableStream.php��e��Ԥ���������vendor/league/csv/src/Info.php ��e ��Dbˤ������ ���vendor/league/csv/src/Stream.php7��e7��?M������'���vendor/league/csv/src/SwapDelimiter.php ��e ��Nm������%���vendor/league/csv/src/SyntaxError.phpz��ez��br������,���vendor/league/csv/src/UnavailableFeature.php"��e"��lv^������&���vendor/league/csv/src/RFC4180Field.php��e��'������(���vendor/league/csv/src/FragmentFinder.php60��e60��C������#���vendor/league/csv/src/Statement.phpU��eU��h?������#���vendor/league/csv/src/Exception.php��e��TCޤ������,���vendor/league/csv/src/UnableToProcessCsv.phpk��ek��1W������ ���vendor/league/csv/src/Writer.php& ��e& ��':������%���vendor/league/csv/src/AbstractCsv.php2��e2��8乤������'���vendor/league/csv/src/HTMLConverter.php��e��HQ������'���vendor/league/csv/src/EscapeFormula.phpz��ez��Խ������8���vendor/league/csv/src/Serializer/SerializationFailed.phpw��ew��(W)������/���vendor/league/csv/src/Serializer/CastToBool.php ��e ��&X������.���vendor/league/csv/src/Serializer/CastToInt.phpk��ek��[3s������0���vendor/league/csv/src/Serializer/CastToFloat.phps��es��@P������1���vendor/league/csv/src/Serializer/CastToString.phpx ��ex ��:Xq������,���vendor/league/csv/src/Serializer/MapCell.php��e�� mp������1���vendor/league/csv/src/Serializer/AfterMapping.php5��e5��&������0���vendor/league/csv/src/Serializer/TypeCasting.php��e��A=������3���vendor/league/csv/src/Serializer/PropertySetter.php;��e;��G>>ߤ������0���vendor/league/csv/src/Serializer/CastToArray.php��e��������/���vendor/league/csv/src/Serializer/ArrayShape.php��e��۪������2���vendor/league/csv/src/Serializer/MappingFailed.php ��e �� ������1���vendor/league/csv/src/Serializer/Denormalizer.phpt?��et?��Yl������:���vendor/league/csv/src/Serializer/DenormalizationFailed.php��e��7{Q������/���vendor/league/csv/src/Serializer/CastToEnum.php��e��9- -������6���vendor/league/csv/src/Serializer/TypeCastingFailed.php��e��|G#������/���vendor/league/csv/src/Serializer/CastToDate.php��e��;Fc}������)���vendor/league/csv/src/Serializer/Type.phpM��eM��R������4���vendor/league/csv/src/Serializer/CallbackCasting.php((��e((��2)������+���vendor/league/csv/src/TabularDataReader.php��e��wJ������&���vendor/league/csv/src/ByteSequence.phpL��eL��������&���vendor/league/csv/src/EncloseField.php ��e �� +������#���vendor/league/csv/src/ResultSet.php7��e7��������%���vendor/composer/InstalledVersions.php?��e?�� 2Ť���������vendor/composer/LICENSE.��e.�� ������%���vendor/composer/autoload_classmap.php���e���L������'���vendor/composer/autoload_namespaces.php���e���/t���������vendor/composer/installed.json ��e ��a9/b������"���vendor/composer/autoload_files.php���e���־d������!���vendor/composer/autoload_psr4.php���e���������!���vendor/composer/autoload_real.php��e��%1���������vendor/composer/installed.php ��e ��ek,���������vendor/composer/ClassLoader.php?��e?��2@u������#���vendor/composer/autoload_static.php��e��Ȥ������"���vendor/composer/platform_check.php��e��Qg������ ���.dockerignore)���e)���=s������ ���composer.json9���e9��� ������ ���Dockerfilec���ec���Y l������ ���composer.lock��e��1������ ���.git/config��e��h���������.git/refs/heads/main)���e)���ɾ���������.git/refs/remotes/origin/main)���e)���ɾ���������.git/refs/remotes/github/main)���e)���-<������$���.git/hooks/prepare-commit-msg.sample��e��60���������.git/hooks/commit-msg.sample��e�����������.git/hooks/pre-rebase.sample"��e"��XQ���������.git/hooks/update.sampleB��eB�����������.git/hooks/post-update.sample���e��� ������"���.git/hooks/pre-merge-commit.sample��e��D?^���������.git/hooks/pre-receive.sample ��e �����������.git/hooks/pre-push.sample^��e^�� ������ ���.git/hooks/pre-applypatch.sample��e��L���������.git/hooks/pre-commit.samplek��ek��2 ������ ���.git/hooks/applypatch-msg.sample��e��O ������"���.git/hooks/push-to-checkout.sample ��e ��������$���.git/hooks/fsmonitor-watchman.sample/��e/��Pc���������.git/info/exclude���e���w=!���������.git/COMMIT_EDITMSG���e���VN /������ ���.git/HEAD���e���cdW������ ���.git/index��e��Voo���������.git/logs/refs/heads/main��e��^ Ԥ������"���.git/logs/refs/remotes/origin/main��e�� 7������"���.git/logs/refs/remotes/github/main���e���YA���������.git/logs/HEAD��e��^ Ԥ������6���.git/objects/64/a233af5e54a14e24ff031f39fa1d714006c3f88��e8��$$������6���.git/objects/d2/e07529f3b534b87f29c6987ffa91bd4c48e379}���e}���$x�$������6���.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904���e���~r������6���.git/objects/b4/addbee94395a367ff92c958c83b17d3fd8cf0c7��e7��$������6���.git/objects/d5/e61518a3c7acf9fb508427eb96dc60096dbc34��e��q$������6���.git/objects/bf/323c5cc8c54b0d559a3afb193697fa46c8068e9���e9���MX$������6���.git/objects/93/1ace1f595ce92602d5cf99e7cdbdcbf70d90b8\��e\��aP$������6���.git/objects/4a/c3e5339bef3ae13f53fafdddb5f078c511deb4 ��e ��q$������6���.git/objects/76/ae5384942f21e395844a85c48aaa7825d7e0bc|���e|��� $������6���.git/objects/94/adbaa37e5fd356e72b526bc1bc9a5403e6a12e��e��)$������6���.git/objects/0c/6b51af1035088f53da6c78a5e35ab0ccabb705���e���7������6���.git/objects/ce/b0b8faeccf55ba8c64c3dc90e0015f8b058a5a=��e=�� $������6���.git/objects/7a/eae98c8147e71a29ad253f3d4190d7852c613c}���e}���B0$������6���.git/objects/02/3745ad00686a461060190430f6bf16232c1810���e���L $������6���.git/objects/19/614200237e55cab4ace007c613909a61a1e9394��e4��$������6���.git/objects/21/9fc99c18f1a922f598c42f54db5376b11127b7���e���.;$������6���.git/objects/32/0f090e73762c3a128d41fac31ea69eb756ea1cW��eW��h$������6���.git/objects/65/894d22eb13c47273927bb96d42594a217a92f66��e6���$������6���.git/objects/b3/d9a7985c567dab76c2990502c81dfc9911d013<���e<���D+$������6���.git/objects/a1/33e9d5026922c6e074025b206c740a4682d781p���ep���I$������6���.git/objects/a4/120c8e7322215a2f47f0ce4e417147364f35f49��e9��xqE$������6���.git/objects/a4/9222cf224a603bd68c3b26e503b958719f0de0}���e}���2uk$������6���.git/objects/83/7d81ced092ba3739cbc79ebc56c7a99265a754V��eV��u7$������6���.git/objects/fe/b1e6102615ddc40abb6bd34a0d9a1862692e488��e8��/0$������6���.git/objects/6e/e29c5e4c96e3b6e79c47f1cec413c8ca937e71}���e}���$������6���.git/objects/f8/65fe16c1761fa95986455e08d790e6f20422e1��e��7Z$������6���.git/objects/4c/7b83900bfb262217669923211e2cee2e561f68���e���) P$������6���.git/objects/31/c161837d5b9a4159bb0030712e29811df55abf ��e ��Oe3$������6���.git/objects/e0/48434fff172c950bab5e80c42ac24f0e4bf2b4 ��e ��gP$������6���.git/objects/9e/617a6f1f5d515134a18733beec733798577bef ��e ��$���������.git/descriptionI���eI���7������ ���index.php ��e ��҂w������ ���README.mdp��ep��;K���������.php-cs-fixer.cache ��e �������� ���make-phar.phpY��eY��.Al?������b0VIM 8.2������ey�H1�simon�����������������������������������endurance�������������������������������~simon/public_html/www/repo/ncpasswords2bitwarden/make-phar.php��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������utf-8 �3210����#"! U�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������tp����������� ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ad��_������� ���������������u��@��$���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������}� echo 'Error: ' . $e->getMe}� echo 'Error: ' . $e->getMessage(), "\n";�} catch (Exception $e) {� echo 'Phar archive created successfully.', "\n";� $phar->stopBuffering();� $phar->setDefaultStub('index.php', 'index.php');� $phar->buildFromDirectory(__DIR__);� $phar = new \Phar('ncpasswords2bitwarden.phar', 0, 'ncpasswords2bitwarden.phar');�try {��<?php�<?php // autoload.php @generated by Composer if (PHP_VERSION_ID < 50600) { if (!headers_sent()) { header('HTTP/1.1 500 Internal Server Error'); } $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; if (!ini_get('display_errors')) { if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { fwrite(STDERR, $err); } elseif (!headers_sent()) { echo $err; } } trigger_error( $err, E_USER_ERROR ); } require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInitcf8212b08cc6e1d25da57aa1d364a1fa::getLoader(); The MIT License (MIT) Copyright (c) 2013 ignace nyamagana butera 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. <?php require __DIR__ . '/src/functions_include.php'; spl_autoload_register(static function (string $class_name): void { $prefix = 'League\Csv\\'; if (!str_starts_with($class_name, $prefix)) { return; } $file = __DIR__ . '/src/' . str_replace('\\', '/', substr($class_name, 11)) . '.php'; if (is_readable($file)) { require $file; } }); { "name": "league/csv", "type": "library", "description" : "CSV data manipulation made easy in PHP", "keywords": ["csv", "import", "export", "read", "write", "filter", "convert", "transform"], "license": "MIT", "homepage" : "https://csv.thephpleague.com", "authors": [ { "name" : "Ignace Nyamagana Butera", "email" : "nyamsprod@gmail.com", "homepage" : "https://github.com/nyamsprod/", "role" : "Developer" } ], "support": { "docs": "https://csv.thephpleague.com", "issues": "https://github.com/thephpleague/csv/issues", "rss": "https://github.com/thephpleague/csv/releases.atom", "source": "https://github.com/thephpleague/csv" }, "funding": [ { "type": "github", "url": "https://github.com/sponsors/nyamsprod" } ], "require": { "php": "^8.1.2", "ext-filter": "*", "ext-json": "*", "ext-mbstring": "*" }, "require-dev": { "ext-dom": "*", "ext-xdebug": "*", "doctrine/collections": "^2.1.4", "friendsofphp/php-cs-fixer": "^v3.22.0", "phpbench/phpbench": "^1.2.15", "phpstan/phpstan": "^1.10.57", "phpstan/phpstan-deprecation-rules": "^1.1.4", "phpstan/phpstan-phpunit": "^1.3.15", "phpstan/phpstan-strict-rules": "^1.5.2", "phpunit/phpunit": "^10.5.9", "symfony/var-dumper": "^6.4.2" }, "autoload": { "psr-4": { "League\\Csv\\": "src" }, "files": ["src/functions_include.php"] }, "scripts": { "benchmark": "phpbench run src --report=default", "phpcs": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix -vvv --diff --dry-run --allow-risky=yes --ansi", "phpcs:fix": "php-cs-fixer fix -vvv --allow-risky=yes --ansi", "phpstan": "phpstan analyse -c phpstan.neon --ansi --memory-limit=192M", "phpunit": "XDEBUG_MODE=coverage phpunit --coverage-text", "phpunit:min": "phpunit --no-coverage", "test": [ "@phpunit", "@phpstan", "@phpcs" ] }, "scripts-descriptions": { "benchmark": "Runs benchmarks on writing and reader CSV documents", "phpcs": "Runs coding style test suite", "phpstan": "Runs complete codebase static analysis", "phpunit": "Runs unit and functional testing", "test": "Runs full test suite" }, "suggest": { "ext-iconv" : "Needed to ease transcoding CSV using iconv stream filters", "ext-dom" : "Required to use the XMLConverter and the HTMLConverter classes" }, "extra": { "branch-alias": { "dev-master": "9.x-dev" } }, "config": { "sort-packages": true } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if (!function_exists('League\Csv\bom_match')) { require __DIR__.'/functions.php'; } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use OutOfRangeException; use php_user_filter; use function array_combine; use function array_map; use function in_array; use function is_numeric; use function mb_convert_encoding; use function mb_list_encodings; use function preg_match; use function sprintf; use function stream_bucket_append; use function stream_bucket_make_writeable; use function stream_filter_register; use function stream_get_filters; use function strtolower; use function substr; /** * Converts resource stream or tabular data content charset. */ class CharsetConverter extends php_user_filter { public const FILTERNAME = 'convert.league.csv'; public const BOM_SEQUENCE = 'bom_sequence'; public const SKIP_BOM_SEQUENCE = 'skip_bom_sequence'; protected string $input_encoding = 'UTF-8'; protected string $output_encoding = 'UTF-8'; protected bool $skipBomSequence = false; /** * Static method to add the stream filter to a {@link Reader} object to handle BOM skipping. */ public static function addBOMSkippingTo(Reader $document, string $output_encoding = 'UTF-8'): Reader { self::register(); $document->addStreamFilter( self::getFiltername(match ($document->getInputBOM()) { ByteSequence::BOM_UTF16_LE => 'UTF-16LE', ByteSequence::BOM_UTF16_BE => 'UTF-16BE', ByteSequence::BOM_UTF32_LE => 'UTF-32LE', ByteSequence::BOM_UTF32_BE => 'UTF-32BE', default => 'UTF-8', }, $output_encoding), [self::BOM_SEQUENCE => self::SKIP_BOM_SEQUENCE] ); return $document; } /** * Static method to add the stream filter to a {@link AbstractCsv} object. */ public static function addTo(AbstractCsv $csv, string $input_encoding, string $output_encoding, array $params = null): AbstractCsv { self::register(); return $csv->addStreamFilter(self::getFiltername($input_encoding, $output_encoding), $params); } /** * Static method to register the class as a stream filter. */ public static function register(): void { $filter_name = self::FILTERNAME.'.*'; if (!in_array($filter_name, stream_get_filters(), true)) { stream_filter_register($filter_name, self::class); } } /** * Static method to return the stream filter filtername. */ public static function getFiltername(string $input_encoding, string $output_encoding): string { return sprintf( '%s.%s/%s', self::FILTERNAME, self::filterEncoding($input_encoding), self::filterEncoding($output_encoding) ); } /** * Filter encoding charset. * * @throws OutOfRangeException if the charset is malformed or unsupported */ protected static function filterEncoding(string $encoding): string { static $encoding_list; if (null === $encoding_list) { $list = mb_list_encodings(); $encoding_list = array_combine(array_map(strtolower(...), $list), $list); } $key = strtolower($encoding); if (isset($encoding_list[$key])) { return $encoding_list[$key]; } throw new OutOfRangeException('The submitted charset '.$encoding.' is not supported by the mbstring extension.'); } public function onCreate(): bool { $prefix = self::FILTERNAME.'.'; if (!str_starts_with($this->filtername, $prefix)) { return false; } $encodings = substr($this->filtername, strlen($prefix)); if (1 !== preg_match(',^(?<input>[-\w]+)/(?<output>[-\w]+)$,', $encodings, $matches)) { return false; } try { $this->input_encoding = self::filterEncoding($matches['input']); $this->output_encoding = self::filterEncoding($matches['output']); $this->skipBomSequence = is_array($this->params) && isset($this->params[self::BOM_SEQUENCE]) && self::SKIP_BOM_SEQUENCE === $this->params[self::BOM_SEQUENCE]; } catch (OutOfRangeException) { return false; } return true; } public function filter($in, $out, &$consumed, bool $closing): int { set_error_handler(fn (int $errno, string $errstr, string $errfile, int $errline) => true); $alreadyRun = false; while (null !== ($bucket = stream_bucket_make_writeable($in))) { $content = $bucket->data; if (!$alreadyRun && $this->skipBomSequence && null !== ($bom = Info::fetchBOMSequence($content))) { $content = substr($content, strlen($bom)); } $alreadyRun = true; $bucket->data = mb_convert_encoding($content, $this->output_encoding, $this->input_encoding); $consumed += $bucket->datalen; stream_bucket_append($out, $bucket); } restore_error_handler(); return PSFS_PASS_ON; } /** * Converts Csv records collection into UTF-8. */ public function convert(iterable $records): iterable { return match (true) { $this->output_encoding === $this->input_encoding => $records, is_array($records) => array_map($this, $records), default => new MapIterator($records, $this), }; } /** * Enable using the class as a formatter for the {@link Writer}. */ public function __invoke(array $record): array { $outputRecord = []; foreach ($record as $offset => $value) { [$newOffset, $newValue] = $this->encodeField($value, $offset); $outputRecord[$newOffset] = $newValue; } return $outputRecord; } /** * Walker method to convert the offset and the value of a CSV record field. */ protected function encodeField(int|float|string|null $value, int|string $offset): array { if (null !== $value && !is_numeric($value)) { $value = mb_convert_encoding($value, $this->output_encoding, $this->input_encoding); } if (!is_numeric($offset)) { $offset = mb_convert_encoding($offset, $this->output_encoding, $this->input_encoding); } return [$offset, $value]; } /** * Sets the records input encoding charset. */ public function inputEncoding(string $encoding): self { $encoding = self::filterEncoding($encoding); if ($encoding === $this->input_encoding) { return $this; } $clone = clone $this; $clone->input_encoding = $encoding; return $clone; } /** * Sets the records output encoding charset. */ public function outputEncoding(string $encoding): self { $encoding = self::filterEncoding($encoding); if ($encoding === $this->output_encoding) { return $this; } $clone = clone $this; $clone->output_encoding = $encoding; return $clone; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use RuntimeException; final class FragmentNotFound extends RuntimeException implements UnableToProcessCsv { } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; /** * A class to insert records into a CSV Document. */ interface TabularDataWriter { /** * Adds multiple records to the CSV document. * * @see TabularDataWriter::insertOne * * @throws CannotInsertRecord If the record can not be inserted * @throws Exception If the record can not be inserted */ public function insertAll(iterable $records): int; /** * Adds a single record to a CSV document. * * A record is an array that can contain scalar type values, NULL values * or objects implementing the __toString method. * * @throws CannotInsertRecord If the record can not be inserted * @throws Exception If the record can not be inserted */ public function insertOne(array $record): int; } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use ArrayIterator; use IteratorIterator; use Traversable; /** * Maps value from an iterator before yielding. * * @internal used internally to modify CSV content */ final class MapIterator extends IteratorIterator { /** @var callable The callback to apply on all InnerIterator current value. */ private $callable; public function __construct(Traversable $iterator, callable $callable) { parent::__construct($iterator); $this->callable = $callable; } public static function fromIterable(iterable $iterator, callable $callable): self { return match (true) { is_array($iterator) => new self(new ArrayIterator($iterator), $callable), default => new self($iterator, $callable), }; } public function current(): mixed { return ($this->callable)(parent::current(), parent::key()); } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; /** * Thrown when a data is not added to the Csv Document. */ class CannotInsertRecord extends Exception { /** The record submitted for insertion. */ protected array $record; /** Validator which did not validate the data. */ protected string $name = ''; /** * Creates an Exception from a record insertion into a stream. */ public static function triggerOnInsertion(array $record): self { $exception = new self('Unable to write record to the CSV document'); $exception->record = $record; return $exception; } /** * Creates an Exception from a Record Validation. */ public static function triggerOnValidation(string $name, array $record): self { $exception = new self('Record validation failed'); $exception->name = $name; $exception->record = $record; return $exception; } /** * Returns the validator name. */ public function getName(): string { return $this->name; } /** * Returns the invalid data submitted. */ public function getRecord(): array { return $this->record; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use DOMAttr; use DOMDocument; use DOMElement; use DOMException; /** * Converts tabular data into a DOMDocument object. */ class XMLConverter { /** XML Root name. */ protected string $root_name = 'csv'; /** XML Node name. */ protected string $record_name = 'row'; /** XML Item name. */ protected string $field_name = 'cell'; /** XML column attribute name. */ protected string $column_attr = ''; /** XML offset attribute name. */ protected string $offset_attr = ''; public static function create(): self { return new self(); } /** * DEPRECATION WARNING! This method will be removed in the next major point release. * * @deprecated since version 9.7.0 * @see XMLConverter::create() */ public function __construct() { } /** * Converts a Record collection into a DOMDocument. */ public function convert(iterable $records): DOMDocument { $doc = new DOMDocument('1.0'); $node = $this->import($records, $doc); $doc->appendChild($node); return $doc; } /** * Creates a new DOMElement related to the given DOMDocument. * * **DOES NOT** attach to the DOMDocument */ public function import(iterable $records, DOMDocument $doc): DOMElement { $root = $doc->createElement($this->root_name); foreach ($records as $offset => $record) { $node = $this->recordToElement($doc, $record, $offset); $root->appendChild($node); } return $root; } /** * Converts a CSV record into a DOMElement and * adds its offset as DOMElement attribute. */ protected function recordToElement(DOMDocument $doc, array $record, int $offset): DOMElement { $node = $doc->createElement($this->record_name); foreach ($record as $node_name => $value) { $item = $this->fieldToElement($doc, (string) $value, $node_name); $node->appendChild($item); } if ('' !== $this->offset_attr) { $node->setAttribute($this->offset_attr, (string) $offset); } return $node; } /** * Converts Cell to Item. * * Converts the CSV item into a DOMElement and adds the item offset * as attribute to the returned DOMElement */ protected function fieldToElement(DOMDocument $doc, string $value, int|string $node_name): DOMElement { $item = $doc->createElement($this->field_name); $item->appendChild($doc->createTextNode($value)); if ('' !== $this->column_attr) { $item->setAttribute($this->column_attr, (string) $node_name); } return $item; } /** * XML root element setter. * * @throws DOMException */ public function rootElement(string $node_name): self { $clone = clone $this; $clone->root_name = $this->filterElementName($node_name); return $clone; } /** * Filters XML element name. * * @throws DOMException If the Element name is invalid */ protected function filterElementName(string $value): string { return (new DOMElement($value))->tagName; } /** * XML Record element setter. * * @throws DOMException */ public function recordElement(string $node_name, string $record_offset_attribute_name = ''): self { $clone = clone $this; $clone->record_name = $this->filterElementName($node_name); $clone->offset_attr = $this->filterAttributeName($record_offset_attribute_name); return $clone; } /** * Filters XML attribute name. * * @param string $value Element name * * @throws DOMException If the Element attribute name is invalid */ protected function filterAttributeName(string $value): string { if ('' === $value) { return $value; } return (new DOMAttr($value))->name; } /** * XML Field element setter. * * @throws DOMException */ public function fieldElement(string $node_name, string $fieldname_attribute_name = ''): self { $clone = clone $this; $clone->field_name = $this->filterElementName($node_name); $clone->column_attr = $this->filterAttributeName($fieldname_attribute_name); return $clone; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use CallbackFilterIterator; use Closure; use Iterator; use JsonSerializable; use League\Csv\Serializer\Denormalizer; use League\Csv\Serializer\MappingFailed; use League\Csv\Serializer\TypeCastingFailed; use SplFileObject; use function array_filter; use function array_unique; use function is_array; use function iterator_count; use function strlen; use function substr; use const STREAM_FILTER_READ; /** * A class to parse and read records from a CSV document. */ class Reader extends AbstractCsv implements TabularDataReader, JsonSerializable { protected const STREAM_FILTER_MODE = STREAM_FILTER_READ; protected ?int $header_offset = null; protected int $nb_records = -1; protected bool $is_empty_records_included = false; /** @var array<string> header record. */ protected array $header = []; /** @var array<callable> callable collection to format the record before reading. */ protected array $formatters = []; public static function createFromPath(string $path, string $open_mode = 'r', $context = null): static { return parent::createFromPath($path, $open_mode, $context); } /** * Adds a record formatter. */ public function addFormatter(callable $formatter): self { $this->formatters[] = $formatter; return $this; } /** * Selects the record to be used as the CSV header. * * Because the header is represented as an array, to be valid * a header MUST contain only unique string value. * * @param int|null $offset the header record offset * * @throws Exception if the offset is a negative integer */ public function setHeaderOffset(?int $offset): static { if ($offset === $this->header_offset) { return $this; } if (null !== $offset && 0 > $offset) { throw InvalidArgument::dueToInvalidHeaderOffset($offset, __METHOD__); } $this->header_offset = $offset; $this->resetProperties(); return $this; } /** * Enables skipping empty records. */ public function skipEmptyRecords(): static { if ($this->is_empty_records_included) { $this->is_empty_records_included = false; $this->nb_records = -1; } return $this; } /** * Disables skipping empty records. */ public function includeEmptyRecords(): static { if (!$this->is_empty_records_included) { $this->is_empty_records_included = true; $this->nb_records = -1; } return $this; } /** * Tells whether empty records are skipped by the instance. */ public function isEmptyRecordsIncluded(): bool { return $this->is_empty_records_included; } protected function resetProperties(): void { parent::resetProperties(); $this->nb_records = -1; $this->header = []; } /** * Returns the header offset. */ public function getHeaderOffset(): ?int { return $this->header_offset; } /** * @throws SyntaxError * * Returns the header record. */ public function getHeader(): array { return match (true) { null === $this->header_offset, [] !== $this->header => $this->header, default => ($this->header = $this->setHeader($this->header_offset)), }; } /** * Determines the CSV record header. * * @throws SyntaxError If the header offset is set and no record is found or is the empty array * * @return array<string> */ protected function setHeader(int $offset): array { $inputBom = ''; $header = $this->seekRow($offset); if (0 === $offset) { $inputBom = $this->getInputBOM(); $header = $this->removeBOM( $header, !$this->is_input_bom_included ? strlen($inputBom) : 0, $this->enclosure ); } return match (true) { [] === $header, [null] === $header, [false] === $header, [''] === $header && 0 === $offset && '' !== $inputBom => throw SyntaxError::dueToHeaderNotFound($offset), default => $header, }; } /** * @throws Exception * * Returns the row at a given offset. */ protected function seekRow(int $offset): array { $this->getDocument()->seek($offset); $record = $this->document->current(); return match (true) { false === $record => [], default => (array) $record, }; } /** * @throws Exception * * Returns the document as an Iterator. */ protected function getDocument(): SplFileObject|Stream { $this->document->setFlags(SplFileObject::READ_CSV | SplFileObject::READ_AHEAD); $this->document->setCsvControl($this->delimiter, $this->enclosure, $this->escape); $this->document->rewind(); return $this->document; } /** * Strips the BOM sequence from a record. * * @param array<string> $record * * @return array<string> */ protected function removeBOM(array $record, int $bom_length, string $enclosure): array { if ([] === $record || !is_string($record[0]) || 0 === $bom_length || strlen($record[0]) < $bom_length) { return $record; } $record[0] = substr($record[0], $bom_length); if ($enclosure.$enclosure !== substr($record[0].$record[0], strlen($record[0]) - 1, 2)) { return $record; } $record[0] = substr($record[0], 1, -1); return $record; } public function fetchColumn(string|int $index = 0): Iterator { return ResultSet::createFromTabularDataReader($this)->fetchColumn($index); } /** * @throws Exception */ public function fetchColumnByName(string $name): Iterator { return ResultSet::createFromTabularDataReader($this)->fetchColumnByName($name); } /** * @throws Exception */ public function fetchColumnByOffset(int $offset = 0): Iterator { return ResultSet::createFromTabularDataReader($this)->fetchColumnByOffset($offset); } public function value(int|string $column = 0): mixed { return ResultSet::createFromTabularDataReader($this)->value($column); } /** * @throws Exception */ public function first(): array { return ResultSet::createFromTabularDataReader($this)->first(); } /** * @throws Exception */ public function nth(int $nth_record): array { return ResultSet::createFromTabularDataReader($this)->nth($nth_record); } /** * @param class-string $className * * @throws Exception */ public function nthAsObject(int $nth, string $className, array $header = []): ?object { return ResultSet::createFromTabularDataReader($this)->nthAsObject($nth, $className, $header); } /** * @param class-string $className * * @throws Exception */ public function firstAsObject(string $className, array $header = []): ?object { return ResultSet::createFromTabularDataReader($this)->firstAsObject($className, $header); } public function fetchPairs($offset_index = 0, $value_index = 1): Iterator { return ResultSet::createFromTabularDataReader($this)->fetchPairs($offset_index, $value_index); } /** * @throws Exception */ public function count(): int { if (-1 === $this->nb_records) { $this->nb_records = iterator_count($this->getRecords()); } return $this->nb_records; } /** * @throws Exception * * @return Iterator<array-key, array<mixed>> */ public function getIterator(): Iterator { return $this->getRecords(); } /** * @throws Exception */ public function jsonSerialize(): array { return array_values([...$this->getRecords()]); } /** * @param Closure(array<mixed>, array-key=): (void|bool|null) $closure */ public function each(Closure $closure): bool { return ResultSet::createFromTabularDataReader($this)->each($closure); } /** * @param Closure(array<mixed>, array-key=): bool $closure */ public function exists(Closure $closure): bool { return ResultSet::createFromTabularDataReader($this)->exists($closure); } /** * @param Closure(TInitial|null, array<mixed>, array-key=): TInitial $closure * @param TInitial|null $initial * * @template TInitial * * @return TInitial|null */ public function reduce(Closure $closure, mixed $initial = null): mixed { return ResultSet::createFromTabularDataReader($this)->reduce($closure, $initial); } /** * @param positive-int $recordsCount * * @throws InvalidArgument * * @return iterable<TabularDataReader> */ public function chunkBy(int $recordsCount): iterable { return ResultSet::createFromTabularDataReader($this)->chunkBy($recordsCount); } /** * @param array<string> $headers */ public function mapHeader(array $headers): TabularDataReader { return Statement::create()->process($this, $headers); } /** * @param Closure(array<mixed>, array-key): bool $closure * * @throws Exception * @throws SyntaxError */ public function filter(Closure $closure): TabularDataReader { return Statement::create()->where($closure)->process($this); } /** * @param int<0, max> $offset * @param int<-1, max> $length * * @throws Exception * @throws SyntaxError */ public function slice(int $offset, int $length = -1): TabularDataReader { return Statement::create()->offset($offset)->limit($length)->process($this); } /** * @param Closure(array<string|null>, array<string|null>): int $orderBy * * @throws Exception * @throws SyntaxError */ public function sorted(Closure $orderBy): TabularDataReader { return Statement::create()->orderBy($orderBy)->process($this); } public function matching(string $expression): iterable { return FragmentFinder::create()->findAll($expression, $this); } public function matchingFirst(string $expression): ?TabularDataReader { return FragmentFinder::create()->findFirst($expression, $this); } /** * @throws SyntaxError * @throws FragmentNotFound */ public function matchingFirstOrFail(string $expression): TabularDataReader { return FragmentFinder::create()->findFirstOrFail($expression, $this); } public function select(string|int ...$columns): TabularDataReader { return Statement::create()->select(...$columns)->process($this); } /** * @param array<string> $header * * @throws Exception * * @return Iterator<array<mixed>> */ public function getRecords(array $header = []): Iterator { return $this->combineHeader( $this->prepareRecords(), $this->prepareHeader($header) ); } /** * @template T of object * @param class-string<T> $className * @param array<string> $header * * @throws Exception * @throws MappingFailed * @throws TypeCastingFailed * * @return iterator<T> */ public function getRecordsAsObject(string $className, array $header = []): Iterator { /** @var array<string> $header */ $header = $this->prepareHeader($header); return Denormalizer::assignAll( $className, $this->combineHeader($this->prepareRecords(), $header), $header ); } /** * @throws Exception */ protected function prepareRecords(): Iterator { $normalized = fn ($record): bool => is_array($record) && ($this->is_empty_records_included || $record !== [null]); $bom = ''; if (!$this->is_input_bom_included) { $bom = $this->getInputBOM(); } $records = $this->stripBOM(new CallbackFilterIterator($this->getDocument(), $normalized), $bom); if (null !== $this->header_offset) { $records = new CallbackFilterIterator($records, fn (array $record, int $offset): bool => $offset !== $this->header_offset); } if ($this->is_empty_records_included) { $records = new MapIterator($records, fn (array $record): array => ([null] === $record) ? [] : $record); } return $records; } /** * Strips the BOM sequence from the returned records if necessary. */ protected function stripBOM(Iterator $iterator, string $bom): Iterator { if ('' === $bom) { return $iterator; } $bom_length = strlen($bom); $mapper = function (array $record, int $index) use ($bom_length): array { if (0 !== $index) { return $record; } $record = $this->removeBOM($record, $bom_length, $this->enclosure); if ([''] === $record) { return [null]; } return $record; }; return new CallbackFilterIterator( new MapIterator($iterator, $mapper), fn (array $record): bool => $this->is_empty_records_included || $record !== [null] ); } /** * @param array<string> $header * * @throws SyntaxError * * @return array<int|string> */ protected function prepareHeader($header = []): array { if ($header !== (array_filter($header, is_string(...)))) { throw SyntaxError::dueToInvalidHeaderColumnNames(); } return $this->computeHeader($header); } /** * Returns the header to be used for iteration. * * @param array<int|string> $header * * @throws SyntaxError If the header contains non unique column name * * @return array<int|string> */ protected function computeHeader(array $header): array { if ([] === $header) { $header = $this->getHeader(); } return match (true) { $header !== array_unique($header) => throw SyntaxError::dueToDuplicateHeaderColumnNames($header), [] !== array_filter(array_keys($header), fn (string|int $value) => !is_int($value) || $value < 0) => throw new SyntaxError('The header mapper indexes should only contain positive integer or 0.'), default => $header, }; } protected function combineHeader(Iterator $iterator, array $header): Iterator { $formatter = fn (array $record): array => array_reduce( $this->formatters, fn (array $record, callable $formatter): array => $formatter($record), $record ); return match ([]) { $header => new MapIterator($iterator, $formatter(...)), default => new MapIterator($iterator, function (array $record) use ($header, $formatter): array { $assocRecord = []; foreach ($header as $offset => $headerName) { $assocRecord[$headerName] = $record[$offset] ?? null; } return $formatter($assocRecord); }), }; } /** * DEPRECATION WARNING! This method will be removed in the next major point release. * * @see Reader::nth() * @deprecated since version 9.9.0 * @codeCoverageIgnore */ public function fetchOne(int $nth_record = 0): array { return $this->nth($nth_record); } /** * DEPRECATION WARNING! This method will be removed in the next major point release. * * @see Reader::getRecordsAsObject() * @deprecated Since version 9.15.0 * @codeCoverageIgnore * * @param class-string $className * @param array<string> $header * * @throws Exception * @throws MappingFailed * @throws TypeCastingFailed */ public function getObjects(string $className, array $header = []): Iterator { return $this->getRecordsAsObject($className, $header); } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use Throwable; /** * InvalidArgument Exception. */ class InvalidArgument extends Exception { /** * DEPRECATION WARNING! This class will be removed in the next major point release. * * @deprecated since version 9.7.0 */ public function __construct(string $message = '', int $code = 0, Throwable $previous = null) { parent::__construct($message, $code, $previous); } public static function dueToInvalidChunkSize(int $length, string $method): self { return new self($method.'() expects the length to be a positive integer '.$length.' given.'); } public static function dueToInvalidHeaderFilename(string $filename): self { return new self('The filename `'.$filename.'` cannot contain the "/" and "\\" characters.'); } public static function dueToInvalidDelimiterCharacter(string $delimiter, string $method): self { return new self($method.'() expects delimiter to be a single character; `'.$delimiter.'` given.'); } public static function dueToInvalidEnclosureCharacter(string $enclosure, string $method): self { return new self($method.'() expects enclosure to be a single character; `'.$enclosure.'` given.'); } public static function dueToInvalidEscapeCharacter(string $escape, string $method): self { return new self($method.'() expects escape to be a single character or an empty string; `'.$escape.'` given.'); } public static function dueToInvalidColumnCount(int $columns_count, string $method): self { return new self($method.'() expects the column count to be greater or equal to -1 '.$columns_count.' given.'); } public static function dueToInvalidHeaderOffset(int $offset, string $method): self { return new self($method.'() expects header offset to be greater or equal to 0; `'.$offset.'` given.'); } public static function dueToInvalidRecordOffset(int $offset, string $method): self { return new self($method.'() expects the submitted offset to be a positive integer or 0, '.$offset.' given'); } public static function dueToInvalidColumnIndex(string|int $index, string $type, string $method): self { return new self($method.'() expects the '.$type.' index to be a valid string or integer, `'.$index.'` given'); } public static function dueToInvalidLimit(int $limit, string $method): self { return new self($method.'() expects the limit to be greater or equal to -1, '.$limit.' given.'); } public static function dueToInvalidSeekingPosition(int $position, string $method): self { return new self($method.'() can\'t seek stream to negative line '.$position); } public static function dueToStreamFilterNotFound(string $filtername): self { return new self('unable to locate filter `'.$filtername.'`'); } public static function dueToInvalidThreshold(int $threshold, string $method): self { return new self($method.'() expects threshold to be null or a valid integer greater or equal to 1'); } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; /** * DEPRECATION WARNING! This class will be removed in the next major point release. * * @deprecated since version 9.7.0 * @see Info::fetchBOMSequence() * @codeCoverageIgnore * * Returns the BOM sequence found at the start of the string. * * If no valid BOM sequence is found an empty string is returned */ function bom_match(string $str): string { return Info::fetchBOMSequence($str) ?? ''; } /** * @param array<string> $delimiters * * @return array<string,int> * @deprecated since version 9.7.0 * @see Info::getDelimiterStats() * @codeCoverageIgnore * * Detect Delimiters usage in a {@link Reader} object. * * Returns a associative array where each key represents * a submitted delimiter and each value the number CSV fields found * when processing at most $limit CSV records with the given delimiter * */ function delimiter_detect(Reader $csv, array $delimiters, int $limit = 1): array { return Info::getDelimiterStats($csv, $delimiters, $limit); } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use function count; /** * Validates column consistency when inserting records into a CSV document. */ class ColumnConsistency { /** * @throws InvalidArgument if the column count is less than -1 */ public function __construct( protected int $columns_count = -1 ) { if ($this->columns_count < -1) { throw InvalidArgument::dueToInvalidColumnCount($this->columns_count, __METHOD__); } } /** * Returns the column count. */ public function getColumnCount(): int { return $this->columns_count; } /** * Tells whether the submitted record is valid. */ public function __invoke(array $record): bool { $count = count($record); if (-1 === $this->columns_count) { $this->columns_count = $count; return true; } return $count === $this->columns_count; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; final class UnavailableStream extends Exception { private function __construct(string $message) { parent::__construct($message); } public static function dueToPathNotFound(string $path): self { return new self('`'.$path.'`: failed to open stream: No such file or directory.'); } public static function dueToForbiddenCloning(string $class_name): self { return new self('An object of class '.$class_name.' cannot be cloned.'); } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use function array_fill_keys; use function array_filter; use function array_reduce; use function array_unique; use function count; use function strlen; use const COUNT_RECURSIVE; final class Info implements ByteSequence { private const BOM_SEQUENCE_LIST = [ ByteSequence::BOM_UTF32_BE, ByteSequence::BOM_UTF32_LE, ByteSequence::BOM_UTF16_BE, ByteSequence::BOM_UTF16_LE, ByteSequence::BOM_UTF8, ]; /** * Returns the BOM sequence found at the start of the string. * * If no valid BOM sequence is found an empty string is returned */ public static function fetchBOMSequence(string $str): ?string { foreach (self::BOM_SEQUENCE_LIST as $sequence) { if (str_starts_with($str, $sequence)) { return $sequence; } } return null; } /** * Detect Delimiters usage in a {@link Reader} object. * * Returns a associative array where each key represents * a submitted delimiter and each value the number CSV fields found * when processing at most $limit CSV records with the given delimiter * * @param array<string> $delimiters * * @return array<string, int> */ public static function getDelimiterStats(Reader $csv, array $delimiters, int $limit = 1): array { $stmt = Statement::create()->offset(0)->limit($limit); $delimiterStats = function (array $stats, string $delimiter) use ($csv, $stmt): array { $csv->setDelimiter($delimiter); $foundRecords = []; foreach ($stmt->process($csv)->getRecords() as $record) { if (1 < count($record)) { $foundRecords[] = $record; } } $stats[$delimiter] = count($foundRecords, COUNT_RECURSIVE); return $stats; }; $currentDelimiter = $csv->getDelimiter(); $currentHeaderOffset = $csv->getHeaderOffset(); $csv->setHeaderOffset(null); $stats = array_reduce( array_unique(array_filter($delimiters, fn (string $value): bool => 1 === strlen($value))), $delimiterStats, array_fill_keys($delimiters, 0) ); $csv->setHeaderOffset($currentHeaderOffset); $csv->setDelimiter($currentDelimiter); return $stats; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use RuntimeException; use SeekableIterator; use SplFileObject; use Stringable; use TypeError; use ValueError; use function array_keys; use function array_walk_recursive; use function fclose; use function feof; use function fflush; use function fgetcsv; use function fopen; use function fpassthru; use function fputcsv; use function fread; use function fseek; use function fwrite; use function get_resource_type; use function gettype; use function is_array; use function is_resource; use function restore_error_handler; use function rewind; use function set_error_handler; use function stream_filter_append; use function stream_filter_remove; use function stream_get_meta_data; use function strlen; use const SEEK_SET; /** * An object-oriented API to handle a PHP stream resource. * * @internal used internally to iterate over a stream resource */ final class Stream implements SeekableIterator { /** @var resource */ private $stream; private bool $is_seekable; private bool $should_close_stream = false; /** @var mixed can be a null, false or a scalar type value. Current iterator value. */ private mixed $value; /** Current iterator key. */ private int $offset; /** Flags for the Document. */ private int $flags = 0; private string $delimiter = ','; private string $enclosure = '"'; private string $escape = '\\'; /** @var array<string, array<resource>> Attached filters. */ private array $filters = []; private int $maxLength = 0; /** * @param resource $stream stream type resource */ private function __construct($stream) { $this->is_seekable = stream_get_meta_data($stream)['seekable']; $this->stream = $stream; } public function __destruct() { array_walk_recursive($this->filters, fn ($filter): bool => @stream_filter_remove($filter)); if ($this->should_close_stream) { set_error_handler(fn (int $errno, string $errstr, string $errfile, int $errline) => true); fclose($this->stream); restore_error_handler(); } unset($this->stream); } public function __clone(): void { throw UnavailableStream::dueToForbiddenCloning(self::class); } public function __debugInfo(): array { return stream_get_meta_data($this->stream) + [ 'delimiter' => $this->delimiter, 'enclosure' => $this->enclosure, 'escape' => $this->escape, 'stream_filters' => array_keys($this->filters), ]; } /** * Returns a new instance from a file path. * * @param resource|null $context * * @throws UnavailableStream if the stream resource can not be created */ public static function createFromPath(string $path, string $open_mode = 'r', $context = null): self { $args = [$path, $open_mode]; if (null !== $context) { $args[] = false; $args[] = $context; } set_error_handler(fn (int $errno, string $errstr, string $errfile, int $errline) => true); $resource = fopen(...$args); restore_error_handler(); if (!is_resource($resource)) { throw UnavailableStream::dueToPathNotFound($path); } $instance = new self($resource); $instance->should_close_stream = true; return $instance; } /** * Returns a new instance from a string. */ public static function createFromString(Stringable|string $content = ''): self { /** @var resource $resource */ $resource = fopen('php://temp', 'r+'); fwrite($resource, (string) $content); $instance = new self($resource); $instance->should_close_stream = true; return $instance; } public static function createFromResource(mixed $stream): self { return match (true) { !is_resource($stream) => throw new TypeError('Argument passed must be a stream resource, '.gettype($stream).' given.'), 'stream' !== ($type = get_resource_type($stream)) => throw new TypeError('Argument passed must be a stream resource, '.$type.' resource given'), default => new self($stream), }; } /** * Returns the URI of the underlying stream. * * @see https://www.php.net/manual/en/splfileinfo.getpathname.php */ public function getPathname(): string { return stream_get_meta_data($this->stream)['uri']; } /** * Appends a filter. * * @see http://php.net/manual/en/function.stream-filter-append.php * * @throws InvalidArgument if the filter can not be appended */ public function appendFilter(string $filtername, int $read_write, array $params = null): void { set_error_handler(fn (int $errno, string $errstr, string $errfile, int $errline) => true); $res = stream_filter_append($this->stream, $filtername, $read_write, $params ?? []); restore_error_handler(); if (!is_resource($res)) { throw InvalidArgument::dueToStreamFilterNotFound($filtername); } $this->filters[$filtername][] = $res; } /** * Sets CSV control. * * @see https://www.php.net/manual/en/splfileobject.setcsvcontrol.php * * @throws InvalidArgument */ public function setCsvControl(string $delimiter = ',', string $enclosure = '"', string $escape = '\\'): void { [$this->delimiter, $this->enclosure, $this->escape] = $this->filterControl($delimiter, $enclosure, $escape, __METHOD__); } /** * Filters CSV control characters. * * @throws InvalidArgument If the CSV control character is not exactly one character. * * @return array{0:string, 1:string, 2:string} */ private function filterControl(string $delimiter, string $enclosure, string $escape, string $caller): array { return match (true) { 1 !== strlen($delimiter) => throw InvalidArgument::dueToInvalidDelimiterCharacter($delimiter, $caller), 1 !== strlen($enclosure) => throw InvalidArgument::dueToInvalidEnclosureCharacter($enclosure, $caller), 1 !== strlen($escape) && '' !== $escape => throw InvalidArgument::dueToInvalidEscapeCharacter($escape, $caller), default => [$delimiter, $enclosure, $escape], }; } /** * Returns CSV control. * * @see https://www.php.net/manual/en/splfileobject.getcsvcontrol.php * * @return array<string> */ public function getCsvControl(): array { return [$this->delimiter, $this->enclosure, $this->escape]; } /** * Sets CSV stream flags. * * @see https://www.php.net/manual/en/splfileobject.setflags.php */ public function setFlags(int $flags): void { $this->flags = $flags; } /** * Writes a field array as a CSV line. * * @see https://www.php.net/manual/en/splfileobject.fputcsv.php * * @throws InvalidArgument If the CSV control character is not exactly one character. */ public function fputcsv(array $fields, string $delimiter = ',', string $enclosure = '"', string $escape = '\\', string $eol = "\n"): int|false { return fputcsv( $this->stream, $fields, ...[...$this->filterControl($delimiter, $enclosure, $escape, __METHOD__), $eol] ); } /** * Gets line number. * * @see https://www.php.net/manual/en/splfileobject.key.php */ public function key(): int { return $this->offset; } /** * Reads next line. * * @see https://www.php.net/manual/en/splfileobject.next.php */ public function next(): void { $this->value = false; $this->offset++; } /** * Rewinds the file to the first line. * * @see https://www.php.net/manual/en/splfileobject.rewind.php * * @throws Exception if the stream resource is not seekable * @throws RuntimeException if rewinding the stream fails. */ public function rewind(): void { if (!$this->is_seekable) { throw UnavailableFeature::dueToMissingStreamSeekability(); } if (false === rewind($this->stream)) { throw new RuntimeException('Unable to rewind the document.'); } $this->offset = 0; $this->value = false; if (SplFileObject::READ_AHEAD === ($this->flags & SplFileObject::READ_AHEAD)) { $this->current(); } } /** * Not at EOF. * * @see https://www.php.net/manual/en/splfileobject.valid.php */ public function valid(): bool { return match (true) { SplFileObject::READ_AHEAD === ($this->flags & SplFileObject::READ_AHEAD) => false !== $this->current(), default => !feof($this->stream), }; } /** * Retrieves the current line of the file. * * @see https://www.php.net/manual/en/splfileobject.current.php */ public function current(): mixed { if (false !== $this->value) { return $this->value; } $this->value = match (true) { SplFileObject::READ_CSV === ($this->flags & SplFileObject::READ_CSV) => $this->getCurrentRecord(), default => $this->getCurrentLine(), }; return $this->value; } public function fgets(): string|false { $arg = [$this->stream]; if (0 < $this->maxLength) { $arg[] = $this->maxLength; } return fgets(...$arg); } /** * Sets the maximum length of a line to be read. * * @see https://www.php.net/manual/en/splfileobject.setmaxlinelen.php */ public function setMaxLineLen(int $maxLength): void { if (0 > $maxLength) { throw new ValueError(' Argument #1 ($maxLength) must be greater than or equal to 0'); } $this->maxLength = $maxLength; } /** * Gets the maximum line length as set by setMaxLineLen. * * @see https://www.php.net/manual/en/splfileobject.getmaxlinelen.php */ public function getMaxLineLen(): int { return $this->maxLength; } /** * Tells whether the end of file has been reached. * * @see https://www.php.net/manual/en/splfileobject.eof.php */ public function eof(): bool { return feof($this->stream); } /** * Retrieves the current line as a CSV Record. */ private function getCurrentRecord(): array|false { $isEmptyLine = SplFileObject::SKIP_EMPTY === ($this->flags & SplFileObject::SKIP_EMPTY); do { $ret = fgetcsv($this->stream, 0, $this->delimiter, $this->enclosure, $this->escape); } while ($isEmptyLine && is_array($ret) && null === $ret[0]); return $ret; } /** * Retrieves the current line. */ private function getCurrentLine(): string|false { $isEmptyLine = SplFileObject::SKIP_EMPTY === ($this->flags & SplFileObject::SKIP_EMPTY); $dropNewLine = SplFileObject::DROP_NEW_LINE === ($this->flags & SplFileObject::DROP_NEW_LINE); $shouldBeIgnored = fn (string|false $line): bool => ($isEmptyLine || $dropNewLine) && (false !== $line && '' === rtrim($line, "\r\n")); $arguments = [$this->stream]; if (0 < $this->maxLength) { $arguments[] = $this->maxLength; } do { $line = fgets(...$arguments); } while ($shouldBeIgnored($line)); if ($dropNewLine && false !== $line) { return rtrim($line, "\r\n"); } return $line; } /** * Seeks to specified line. * * @see https://www.php.net/manual/en/splfileobject.seek.php * * @throws Exception if the position is negative */ public function seek(int $offset): void { if ($offset < 0) { throw InvalidArgument::dueToInvalidSeekingPosition($offset, __METHOD__); } $this->rewind(); while ($this->key() !== $offset && $this->valid()) { $this->current(); $this->next(); } if (0 !== $offset) { $this->offset--; } $this->current(); } /** * Outputs all remaining data on a file pointer. * * @see https://www.php.net/manual/en/splfileobject.fpassthru.php */ public function fpassthru(): int|false { return fpassthru($this->stream); } /** * Reads from file. * * @see https://www.php.net/manual/en/splfileobject.fread.php * * @param int<0, max> $length The number of bytes to read */ public function fread(int $length): string|false { return fread($this->stream, $length); } /** * Seeks to a position. * * @see https://www.php.net/manual/en/splfileobject.fseek.php * * @throws Exception if the stream resource is not seekable */ public function fseek(int $offset, int $whence = SEEK_SET): int { return match (true) { !$this->is_seekable => throw UnavailableFeature::dueToMissingStreamSeekability(), default => fseek($this->stream, $offset, $whence), }; } /** * Write to stream. * * @see http://php.net/manual/en/SplFileObject.fwrite.php */ public function fwrite(string $str, int $length = null): int|false { $args = [$this->stream, $str]; if (null !== $length) { $args[] = $length; } return fwrite(...$args); } /** * Flushes the output to a file. * * @see https://www.php.net/manual/en/splfileobject.fflush.php */ public function fflush(): bool { return fflush($this->stream); } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use php_user_filter; use function in_array; use function str_replace; use function stream_bucket_append; use function stream_bucket_make_writeable; use function stream_filter_register; use function stream_get_filters; use const PSFS_PASS_ON; final class SwapDelimiter extends php_user_filter { private const FILTER_NAME = 'string.league.csv.delimiter'; public const MODE_READ = 'read'; public const MODE_WRITE = 'write'; private string $search; private string $replace; public static function getFiltername(): string { return self::FILTER_NAME; } /** * Static method to register the class as a stream filter. */ public static function register(): void { if (!in_array(self::FILTER_NAME, stream_get_filters(), true)) { stream_filter_register(self::FILTER_NAME, self::class); } } /** * Static method to attach the stream filter to a CSV Reader or Writer instance. */ public static function addTo(AbstractCsv $csv, string $inputDelimiter): void { self::register(); $csv->addStreamFilter(self::getFiltername(), [ 'mb_separator' => $inputDelimiter, 'separator' => $csv->getDelimiter(), 'mode' => $csv instanceof Writer ? self::MODE_WRITE : self::MODE_READ, ]); } public function onCreate(): bool { if (self::FILTER_NAME !== $this->filtername) { return false; } if (!is_array($this->params)) { return false; } $mode = $this->params['mode'] ?? ''; [$this->search, $this->replace] = match ($mode) { self::MODE_READ => [trim($this->params['mb_separator'] ?? ''), trim($this->params['separator'] ?? '')], self::MODE_WRITE => [trim($this->params['separator'] ?? ''), trim($this->params['mb_separator'] ?? '')], default => ['', ''], }; return !in_array('', [$this->replace, $this->search], true); } public function filter($in, $out, &$consumed, bool $closing): int { while (null !== ($bucket = stream_bucket_make_writeable($in))) { $content = $bucket->data; $bucket->data = str_replace($this->search, $this->replace, $content); $consumed += $bucket->datalen; stream_bucket_append($out, $bucket); } return PSFS_PASS_ON; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use Throwable; use function array_count_values; use function array_filter; use function array_keys; /** * SyntaxError Exception. */ class SyntaxError extends Exception { /** * @var array<string> */ protected array $duplicateColumnNames = []; /** * DEPRECATION WARNING! This class will be removed in the next major point release. * * @deprecated since version 9.7.0 */ public function __construct(string $message = '', int $code = 0, Throwable $previous = null) { parent::__construct($message, $code, $previous); } public static function dueToHeaderNotFound(int $offset): self { return new self('The header record does not exist or is empty at offset: `'.$offset.'`'); } public static function dueToInvalidHeaderColumnNames(): self { return new self('The header record contains non string colum names.'); } public static function dueToDuplicateHeaderColumnNames(array $header): self { $instance = new self('The header record contains duplicate column names.'); $instance->duplicateColumnNames = array_keys(array_filter(array_count_values($header), fn (int $value): bool => $value > 1)); return $instance; } public function duplicateColumnNames(): array { return $this->duplicateColumnNames; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use Throwable; /** * StreamFilterSupportMissing Exception. */ class UnavailableFeature extends Exception { /** * DEPRECATION WARNING! This class will be removed in the next major point release. * * @deprecated since version 9.7.0 */ public function __construct(string $message = '', int $code = 0, Throwable $previous = null) { parent::__construct($message, $code, $previous); } public static function dueToUnsupportedStreamFilterApi(string $className): self { return new self('The stream filter API can not be used with a '.$className.' instance.'); } public static function dueToMissingStreamSeekability(): self { return new self('stream does not support seeking.'); } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use InvalidArgumentException; use php_user_filter; use TypeError; use function array_map; use function in_array; use function is_string; use function str_replace; use function strcspn; use function stream_bucket_append; use function stream_bucket_make_writeable; use function stream_filter_register; use function stream_get_filters; use function strlen; use const STREAM_FILTER_READ; use const STREAM_FILTER_WRITE; /** * A stream filter to conform the CSV field to RFC4180. * * DEPRECATION WARNING! This class will be removed in the next major point release * * @deprecated since version 9.2.0 * @see AbstractCsv::setEscape * * @see https://tools.ietf.org/html/rfc4180#section-2 */ class RFC4180Field extends php_user_filter { public const FILTERNAME = 'convert.league.csv.rfc4180'; /** * The value being search for. * * @var array<string> */ protected array $search; /** * The replacement value that replace found $search values. * * @var array<string> */ protected array $replace; /** * Characters that triggers enclosure with PHP fputcsv. */ protected static string $force_enclosure = "\n\r\t "; /** * Static method to add the stream filter to a {@link AbstractCsv} object. */ public static function addTo(AbstractCsv $csv, string $whitespace_replace = ''): AbstractCsv { self::register(); $params = [ 'enclosure' => $csv->getEnclosure(), 'escape' => $csv->getEscape(), 'mode' => $csv->supportsStreamFilterOnWrite() ? STREAM_FILTER_WRITE : STREAM_FILTER_READ, ]; if ($csv instanceof Writer && '' !== $whitespace_replace) { self::addFormatterTo($csv, $whitespace_replace); $params['whitespace_replace'] = $whitespace_replace; } return $csv->addStreamFilter(self::FILTERNAME, $params); } /** * Add a formatter to the {@link Writer} object to format the record * field to avoid enclosure around a field with an empty space. */ public static function addFormatterTo(Writer $csv, string $whitespace_replace): Writer { if ('' == $whitespace_replace || strlen($whitespace_replace) !== strcspn($whitespace_replace, self::$force_enclosure)) { throw new InvalidArgumentException('The sequence contains a character that enforces enclosure or is a CSV control character or is an empty string.'); } $mapper = fn ($value) => is_string($value) ? str_replace(' ', $whitespace_replace, $value) : $value; return $csv->addFormatter(fn (array $record): array => array_map($mapper, $record)); } /** * Static method to register the class as a stream filter. */ public static function register(): void { if (!in_array(self::FILTERNAME, stream_get_filters(), true)) { stream_filter_register(self::FILTERNAME, self::class); } } /** * Static method to return the stream filter filtername. */ public static function getFiltername(): string { return self::FILTERNAME; } /** * @param resource $in * @param resource $out * @param int $consumed */ public function filter($in, $out, &$consumed, bool $closing): int { while (null !== ($bucket = stream_bucket_make_writeable($in))) { $bucket->data = str_replace($this->search, $this->replace, $bucket->data); $consumed += $bucket->datalen; stream_bucket_append($out, $bucket); } return PSFS_PASS_ON; } public function onCreate(): bool { if (!is_array($this->params)) { throw new TypeError('The filter parameters must be an array.'); } static $mode_list = [STREAM_FILTER_READ => 1, STREAM_FILTER_WRITE => 1]; $state = isset($this->params['enclosure'], $this->params['escape'], $this->params['mode'], $mode_list[$this->params['mode']]) && 1 === strlen($this->params['enclosure']) && 1 === strlen($this->params['escape']); if (false === $state) { return false; } $this->search = [$this->params['escape'].$this->params['enclosure']]; $this->replace = [$this->params['enclosure'].$this->params['enclosure']]; if (STREAM_FILTER_WRITE !== $this->params['mode']) { return true; } $this->search = [$this->params['escape'].$this->params['enclosure']]; $this->replace = [$this->params['escape'].$this->params['enclosure'].$this->params['enclosure']]; if ($this->isValidSequence($this->params)) { $this->search[] = $this->params['whitespace_replace']; $this->replace[] = ' '; } return true; } /** * @codeCoverageIgnore * Validate params property. */ protected function isValidParams(array $params): bool { static $mode_list = [STREAM_FILTER_READ => 1, STREAM_FILTER_WRITE => 1]; return isset($params['enclosure'], $params['escape'], $params['mode'], $mode_list[$params['mode']]) && 1 === strlen($params['enclosure']) && 1 === strlen($params['escape']); } /** * Is Valid White space replaced sequence. */ protected function isValidSequence(array $params): bool { return isset($params['whitespace_replace']) && strlen($params['whitespace_replace']) === strcspn($params['whitespace_replace'], self::$force_enclosure); } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use function array_filter; use function array_map; use function array_reduce; use function count; use function explode; use function filter_var; use function preg_match; use function range; use const FILTER_VALIDATE_INT; /** * @phpstan-type selection array{selection:string, start:int<-1, max>, end:?int, length:int, columns:array<int>} */ class FragmentFinder { private const REGEXP_URI_FRAGMENT = ',^(?<type>row|cell|col)=(?<selections>.*)$,i'; private const REGEXP_ROWS_COLUMNS_SELECTION = '/^(?<start>\d+)(-(?<end>\d+|\*))?$/'; private const REGEXP_CELLS_SELECTION = '/^(?<csr>\d+),(?<csc>\d+)(-(?<end>((?<cer>\d+),(?<cec>\d+))|\*))?$/'; private const TYPE_ROW = 'row'; private const TYPE_COLUMN = 'col'; private const TYPE_UNKNOWN = 'unknown'; public static function create(): self { return new self(); } /** * @throws SyntaxError * * @return iterable<int, TabularDataReader> */ public function findAll(string $expression, TabularDataReader $tabularDataReader): iterable { return $this->find($this->parseExpression($expression, $tabularDataReader), $tabularDataReader); } /** * @throws SyntaxError */ public function findFirst(string $expression, TabularDataReader $tabularDataReader): ?TabularDataReader { $fragment = $this->find($this->parseExpression($expression, $tabularDataReader), $tabularDataReader)[0]; return match ([]) { $fragment->first() => null, default => $fragment, }; } /** * @throws SyntaxError * @throws FragmentNotFound if the expression can not be parsed */ public function findFirstOrFail(string $expression, TabularDataReader $tabularDataReader): TabularDataReader { $parsedExpression = $this->parseExpression($expression, $tabularDataReader); if ([] !== array_filter($parsedExpression['selections'], fn (array $selection) => -1 === $selection['start'])) { throw new FragmentNotFound('The expression `'.$expression.'` contains an invalid or an unsupported selection for the tabular data.'); } $fragment = $this->find($parsedExpression, $tabularDataReader)[0]; return match ([]) { $fragment->first() => throw new FragmentNotFound('No fragment found in the tabular data with the expression `'.$expression.'`.'), default => $fragment, }; } /** * @param array{type:string, selections:non-empty-array<selection>} $parsedExpression * * @throws SyntaxError * * @return array<int, TabularDataReader> */ private function find(array $parsedExpression, TabularDataReader $tabularDataReader): array { ['type' => $type, 'selections' => $selections] = $parsedExpression; $selections = array_filter($selections, fn (array $selection) => -1 !== $selection['start']); if ([] === $selections) { return [ResultSet::createFromRecords()]; } if (self::TYPE_ROW === $type) { $rowFilter = fn (array $record, int $offset): bool => [] !== array_filter( $selections, fn (array $selection) => $offset >= $selection['start'] && (null === $selection['end'] || $offset <= $selection['end']) ); return [Statement::create()->where($rowFilter)->process($tabularDataReader)]; } if (self::TYPE_COLUMN === $type) { $columns = array_reduce( $selections, fn (array $columns, array $selection) => [...$columns, ...$selection['columns']], [] ); return [match ([]) { $columns => ResultSet::createFromRecords(), default => Statement::create()->select(...$columns)->process($tabularDataReader), }]; } return array_map( fn (array $selection) => Statement::create() ->select(...$selection['columns']) ->offset($selection['start']) ->limit($selection['length']) ->process($tabularDataReader), $selections ); } /** * @return array{type:string, selections:non-empty-array<selection>} */ private function parseExpression(string $expression, TabularDataReader $tabularDataReader): array { if (1 !== preg_match(self::REGEXP_URI_FRAGMENT, $expression, $matches)) { return [ 'type' => self::TYPE_UNKNOWN, 'selections' => [ [ 'selection' => $expression, 'start' => -1, 'end' => null, 'length' => -1, 'columns' => [], ], ], ]; } $type = strtolower($matches['type']); /** @var non-empty-array<selection> $res */ $res = array_reduce( explode(';', $matches['selections']), fn (array $selections, string $selection): array => [...$selections, match ($type) { self::TYPE_ROW => $this->parseRowSelection($selection), self::TYPE_COLUMN => $this->parseColumnSelection($selection, $tabularDataReader), default => $this->parseCellSelection($selection, $tabularDataReader), }], [] ); return [ 'type' => $type, 'selections' => $res, ]; } /** * @return selection */ private function parseRowSelection(string $selection): array { [$start, $end] = $this->parseRowColumnSelection($selection); return match (true) { -1 === $start, null === $end => [ 'selection' => $selection, 'start' => $start, 'end' => $start, 'length' => 1, 'columns' => [], ], '*' === $end => [ 'selection' => $selection, 'start' => $start, 'end' => null, 'length' => -1, 'columns' => [], ], default => [ 'selection' => $selection, 'start' => $start, 'end' => $end, 'length' => $end - $start + 1, 'columns' => [], ], }; } /** * @return selection */ private function parseColumnSelection(string $selection, TabularDataReader $tabularDataReader): array { [$start, $end] = $this->parseRowColumnSelection($selection); $header = $tabularDataReader->getHeader(); if ([] === $header) { $header = $tabularDataReader->first(); } $nbColumns = count($header); return match (true) { -1 === $start, $start >= $nbColumns => [ 'selection' => $selection, 'start' => -1, 'end' => null, 'length' => -1, 'columns' => [], ], null === $end => [ 'selection' => $selection, 'start' => 0, 'end' => null, 'length' => -1, 'columns' => [$start], ], '*' === $end, $end > ($nbColumns - 1) => [ 'selection' => $selection, 'start' => 0, 'end' => null, 'length' => -1, 'columns' => range($start, $nbColumns - 1), ], default => [ 'selection' => $selection, 'start' => 0, 'end' => $end, 'length' => -1, 'columns' => range($start, $end), ], }; } /** * @return array{int<-1, max>, int|null|'*'} */ private function parseRowColumnSelection(string $selection): array { if (1 !== preg_match(self::REGEXP_ROWS_COLUMNS_SELECTION, $selection, $found)) { return [-1, 0]; } $start = $found['start']; $end = $found['end'] ?? null; $start = filter_var($start, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]); if (false === $start) { return [-1, 0]; } --$start; if (null === $end || '*' === $end) { return [$start, $end]; } $end = filter_var($end, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]); if (false === $end) { return [-1, 0]; } --$end; if ($end <= $start) { return [-1, 0]; } return [$start, $end]; } /** * @return selection */ private function parseCellSelection(string $selection, TabularDataReader $tabularDataReader): array { if (1 !== preg_match(self::REGEXP_CELLS_SELECTION, $selection, $found)) { return [ 'selection' => $selection, 'start' => -1, 'end' => null, 'length' => 1, 'columns' => [], ]; } $cellStartRow = filter_var($found['csr'], FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]); $cellStartCol = filter_var($found['csc'], FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]); if (false === $cellStartRow || false === $cellStartCol) { return [ 'selection' => $selection, 'start' => -1, 'end' => null, 'length' => 1, 'columns' => [], ]; } --$cellStartRow; --$cellStartCol; $header = $tabularDataReader->getHeader(); if ([] === $header) { $header = $tabularDataReader->first(); } $nbColumns = count($header); if ($cellStartCol > $nbColumns - 1) { return [ 'selection' => $selection, 'start' => -1, 'end' => null, 'length' => 1, 'columns' => [], ]; } $cellEnd = $found['end'] ?? null; if (null === $cellEnd) { return [ 'selection' => $selection, 'start' => $cellStartRow, 'end' => null, 'length' => 1, 'columns' => [$cellStartCol], ]; } if ('*' === $cellEnd) { return [ 'selection' => $selection, 'start' => $cellStartRow, 'end' => null, 'length' => -1, 'columns' => range($cellStartCol, $nbColumns - 1), ]; } $cellEndRow = filter_var($found['cer'], FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]); $cellEndCol = filter_var($found['cec'], FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]); if (false === $cellEndRow || false === $cellEndCol) { return [ 'selection' => $selection, 'start' => -1, 'end' => null, 'length' => 1, 'columns' => [], ]; } --$cellEndRow; --$cellEndCol; if ($cellEndRow < $cellStartRow || $cellEndCol < $cellStartCol) { return [ 'selection' => $selection, 'start' => -1, 'end' => null, 'length' => 1, 'columns' => [], ]; } return [ 'selection' => $selection, 'start' => $cellStartRow, 'end' => $cellEndRow, 'length' => $cellEndRow - $cellStartRow + 1, 'columns' => range($cellStartCol, ($cellEndCol > $nbColumns - 1) ? $nbColumns - 1 : $cellEndCol), ]; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use ArrayIterator; use CallbackFilterIterator; use Iterator; use LimitIterator; use OutOfBoundsException; use function array_key_exists; use function array_reduce; use function array_search; use function array_values; use function is_string; /** * Criteria to filter a {@link TabularDataReader} object. */ class Statement { /** @var array<callable> Callables to filter the iterator. */ protected array $where = []; /** @var array<callable> Callables to sort the iterator. */ protected array $order_by = []; /** iterator Offset. */ protected int $offset = 0; /** iterator maximum length. */ protected int $limit = -1; /** @var array<string|int> */ protected array $select = []; /** * @throws Exception */ public static function create(callable $where = null, int $offset = 0, int $limit = -1): self { $stmt = new self(); if (null !== $where) { $stmt = $stmt->where($where); } return $stmt->offset($offset)->limit($limit); } /** * Sets the Iterator element columns. */ public function select(string|int ...$columns): self { if ($columns === $this->select) { return $this; } $clone = clone $this; $clone->select = $columns; return $clone; } /** * Sets the Iterator filter method. */ public function where(callable $where): self { $clone = clone $this; $clone->where[] = $where; return $clone; } /** * Sets an Iterator sorting callable function. */ public function orderBy(callable $order_by): self { $clone = clone $this; $clone->order_by[] = $order_by; return $clone; } /** * Sets LimitIterator Offset. * * @throws Exception if the offset is less than 0 */ public function offset(int $offset): self { if (0 > $offset) { throw InvalidArgument::dueToInvalidRecordOffset($offset, __METHOD__); } if ($offset === $this->offset) { return $this; } $clone = clone $this; $clone->offset = $offset; return $clone; } /** * Sets LimitIterator Count. * * @throws Exception if the limit is less than -1 */ public function limit(int $limit): self { if (-1 > $limit) { throw InvalidArgument::dueToInvalidLimit($limit, __METHOD__); } if ($limit === $this->limit) { return $this; } $clone = clone $this; $clone->limit = $limit; return $clone; } /** * Executes the prepared Statement on the {@link Reader} object. * * @param array<string> $header an optional header to use instead of the CSV document header * * @throws InvalidArgument * @throws SyntaxError */ public function process(TabularDataReader $tabular_data, array $header = []): TabularDataReader { if ([] === $header) { $header = $tabular_data->getHeader(); } $iterator = $tabular_data->getRecords($header); $iterator = $this->applyFilter($iterator); $iterator = $this->buildOrderBy($iterator); $iterator = new LimitIterator($iterator, $this->offset, $this->limit); return $this->applySelect($iterator, $header); } /** * Filters elements of an Iterator using a callback function. */ protected function applyFilter(Iterator $iterator): Iterator { $filter = function (array $record, string|int $key): bool { foreach ($this->where as $where) { if (true !== $where($record, $key)) { return false; } } return true; }; return new CallbackFilterIterator($iterator, $filter); } /** * Sorts the Iterator. */ protected function buildOrderBy(Iterator $iterator): Iterator { if ([] === $this->order_by) { return $iterator; } $compare = function (array $record_a, array $record_b): int { foreach ($this->order_by as $callable) { if (0 !== ($cmp = $callable($record_a, $record_b))) { return $cmp; } } return $cmp ?? 0; }; $class = new class () extends ArrayIterator { public function seek(int $offset): void { try { parent::seek($offset); } catch (OutOfBoundsException) { return; } } }; /** @var ArrayIterator<array-key, array<string|null>> $it */ $it = new $class([...$iterator]); $it->uasort($compare); return $it; } /** * * @throws InvalidArgument * @throws SyntaxError */ protected function applySelect(Iterator $records, array $recordsHeader): TabularDataReader { if ([] === $this->select) { return new ResultSet($records, $recordsHeader); } $hasHeader = [] !== $recordsHeader; $selectColumn = function (array $header, string|int $field) use ($recordsHeader, $hasHeader): array { if (is_string($field)) { $index = array_search($field, $recordsHeader, true); if (false === $index) { throw InvalidArgument::dueToInvalidColumnIndex($field, 'offset', __METHOD__); } $header[$index] = $field; return $header; } if ($hasHeader && !array_key_exists($field, $recordsHeader)) { throw InvalidArgument::dueToInvalidColumnIndex($field, 'offset', __METHOD__); } $header[$field] = $recordsHeader[$field] ?? $field; return $header; }; /** @var array<string> $header */ $header = array_reduce($this->select, $selectColumn, []); $records = new MapIterator($records, function (array $record) use ($header): array { $element = []; $row = array_values($record); foreach ($header as $offset => $headerName) { $element[$headerName] = $row[$offset] ?? null; } return $element; }); return new ResultSet($records, $hasHeader ? $header : []); } /** * Filters elements of an Iterator using a callback function. * * DEPRECATION WARNING! This method will be removed in the next major point release. * * @see Statement::applyFilter() * @deprecated Since version 9.15.0 * @codeCoverageIgnore */ protected function filter(Iterator $iterator, callable $callable): CallbackFilterIterator { return new CallbackFilterIterator($iterator, $callable); } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use Exception as PhpException; /** * League Csv Base Exception. */ class Exception extends PhpException implements UnableToProcessCsv { } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use Throwable; interface UnableToProcessCsv extends Throwable { } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use function array_map; use function array_reduce; use function implode; use function restore_error_handler; use function set_error_handler; use function str_replace; use const STREAM_FILTER_WRITE; /** * A class to insert records into a CSV Document. */ class Writer extends AbstractCsv implements TabularDataWriter { protected const STREAM_FILTER_MODE = STREAM_FILTER_WRITE; /** @var array<callable> callable collection to format the record before insertion. */ protected array $formatters = []; /** @var array<callable> callable collection to validate the record before insertion. */ protected array $validators = []; protected string $newline = "\n"; protected int $flush_counter = 0; protected ?int $flush_threshold = null; protected bool $enclose_all = false; /** @var array{0:array<string>,1:array<string>} */ protected array $enclosure_replace; protected function resetProperties(): void { parent::resetProperties(); $this->enclosure_replace = [ [$this->enclosure, $this->escape.$this->enclosure.$this->enclosure], [$this->enclosure.$this->enclosure, $this->escape.$this->enclosure], ]; } /** * Returns the current end of line sequence characters. */ public function getEndOfLine(): string { return $this->newline; } /** * Returns the flush threshold. */ public function getFlushThreshold(): ?int { return $this->flush_threshold; } /** * Tells whether new entries will all be enclosed on writing. */ public function encloseAll(): bool { return $this->enclose_all; } /** * Adds multiple records to the CSV document. * @see Writer::insertOne * * @throws CannotInsertRecord * @throws Exception */ public function insertAll(iterable $records): int { $bytes = 0; foreach ($records as $record) { $bytes += $this->insertOne($record); } $this->flush_counter = 0; $this->document->fflush(); return $bytes; } /** * Adds a single record to a CSV document. * * A record is an array that can contain scalar type values, NULL values * or objects implementing the __toString method. * * @throws CannotInsertRecord If the record can not be inserted * @throws Exception If the record can not be inserted */ public function insertOne(array $record): int { $insert = fn (array $record): int|false => match (true) { $this->enclose_all => $this->document->fwrite(implode( $this->delimiter, array_map( fn ($content) => $this->enclosure.$content.$this->enclosure, str_replace($this->enclosure_replace[0], $this->enclosure_replace[1], $record) ) ).$this->newline), default => $this->document->fputcsv($record, $this->delimiter, $this->enclosure, $this->escape, $this->newline), }; $record = array_reduce($this->formatters, fn (array $record, callable $formatter): array => $formatter($record), $record); $this->validateRecord($record); set_error_handler(fn (int $errno, string $errstr, string $errfile, int $errline) => true); $bytes = $insert($record); restore_error_handler(); if (false === $bytes) { throw CannotInsertRecord::triggerOnInsertion($record); } if (null === $this->flush_threshold) { return $bytes; } ++$this->flush_counter; if (0 === $this->flush_counter % $this->flush_threshold) { $this->flush_counter = 0; $this->document->fflush(); } return $bytes; } /** * Validates a record. * * @throws CannotInsertRecord If the validation failed */ protected function validateRecord(array $record): void { foreach ($this->validators as $name => $validator) { if (true !== $validator($record)) { throw CannotInsertRecord::triggerOnValidation($name, $record); } } } /** * Adds a record formatter. */ public function addFormatter(callable $formatter): self { $this->formatters[] = $formatter; return $this; } /** * Adds a record validator. */ public function addValidator(callable $validator, string $validator_name): self { $this->validators[$validator_name] = $validator; return $this; } /** * Sets the end of line sequence. */ public function setEndOfLine(string $endOfLine): self { $this->newline = $endOfLine; return $this; } /** * Sets the flush threshold. * * @throws InvalidArgument if the threshold is a integer less than 1 */ public function setFlushThreshold(?int $threshold): self { if ($threshold === $this->flush_threshold) { return $this; } if (null !== $threshold && 1 > $threshold) { throw InvalidArgument::dueToInvalidThreshold($threshold, __METHOD__); } $this->flush_threshold = $threshold; $this->flush_counter = 0; $this->document->fflush(); return $this; } public function relaxEnclosure(): self { $this->enclose_all = false; return $this; } public function forceEnclosure(): self { $this->enclose_all = true; return $this; } /** * DEPRECATION WARNING! This method will be removed in the next major point release. * * @deprecated since version 9.8.0 * @codeCoverageIgnore * * Format a record. * * The returned array must contain * - scalar types values, * - NULL values, * - or objects implementing the __toString() method. */ protected function formatRecord(array $record, callable $formatter): array { return $formatter($record); } /** * DEPRECATION WARNING! This method will be removed in the next major point release. * * @deprecated Since version 9.9.0 * @codeCoverageIgnore * * Adds a single record to a CSV Document using PHP algorithm. * * @see https://php.net/manual/en/function.fputcsv.php */ protected function addRecord(array $record): int|false { return $this->document->fputcsv($record, $this->delimiter, $this->enclosure, $this->escape, $this->newline); } /** * DEPRECATION WARNING! This method will be removed in the next major point release. * * @deprecated Since version 9.9.0 * @codeCoverageIgnore * * Applies post insertion actions. */ protected function consolidate(): int { if (null === $this->flush_threshold) { return 0; } ++$this->flush_counter; if (0 === $this->flush_counter % $this->flush_threshold) { $this->flush_counter = 0; $this->document->fflush(); } return 0; } /** * DEPRECATION WARNING! This method will be removed in the next major point release. * * @see Writer::getEndOfLine() * @deprecated Since version 9.10.0 * @codeCoverageIgnore * * Returns the current newline sequence characters. */ public function getNewline(): string { return $this->getEndOfLine(); } /** * DEPRECATION WARNING! This method will be removed in the next major point release. * * @see Writer::setEndOfLine() * @deprecated Since version 9.10.0 * @codeCoverageIgnore * * Sets the newline sequence. */ public function setNewline(string $newline): self { return $this->setEndOfLine($newline); } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use Generator; use RuntimeException; use SplFileObject; use Stringable; use function filter_var; use function get_class; use function mb_strlen; use function rawurlencode; use function sprintf; use function str_replace; use function str_split; use function strcspn; use function strlen; use const FILTER_FLAG_STRIP_HIGH; use const FILTER_FLAG_STRIP_LOW; use const FILTER_UNSAFE_RAW; /** * An abstract class to enable CSV document loading. */ abstract class AbstractCsv implements ByteSequence { protected const STREAM_FILTER_MODE = STREAM_FILTER_READ; /** @var array<string, bool> collection of stream filters. */ protected array $stream_filters = []; protected ?string $input_bom = null; protected string $output_bom = ''; protected string $delimiter = ','; protected string $enclosure = '"'; protected string $escape = '\\'; protected bool $is_input_bom_included = false; /** * @final This method should not be overwritten in child classes */ protected function __construct(protected readonly SplFileObject|Stream $document) { [$this->delimiter, $this->enclosure, $this->escape] = $this->document->getCsvControl(); $this->resetProperties(); } /** * Reset dynamic object properties to improve performance. */ protected function resetProperties(): void { } /** * @throws UnavailableStream */ public function __clone() { throw UnavailableStream::dueToForbiddenCloning(static::class); } /** * Returns a new instance from a SplFileObject. */ public static function createFromFileObject(SplFileObject $file): static { return new static($file); } /** * Returns a new instance from a PHP resource stream. * * @param resource $stream */ public static function createFromStream($stream): static { return new static(Stream::createFromResource($stream)); } /** * Returns a new instance from a string. */ public static function createFromString(Stringable|string $content = ''): static { return new static(Stream::createFromString((string) $content)); } /** * Returns a new instance from a file path. * * @param resource|null $context the resource context * * @throws UnavailableStream */ public static function createFromPath(string $path, string $open_mode = 'r+', $context = null): static { return new static(Stream::createFromPath($path, $open_mode, $context)); } /** * Returns the current field delimiter. */ public function getDelimiter(): string { return $this->delimiter; } /** * Returns the current field enclosure. */ public function getEnclosure(): string { return $this->enclosure; } /** * Returns the pathname of the underlying document. */ public function getPathname(): string { return $this->document->getPathname(); } /** * Returns the current field escape character. */ public function getEscape(): string { return $this->escape; } /** * Returns the BOM sequence in use on Output methods. */ public function getOutputBOM(): string { return $this->output_bom; } /** * Returns the BOM sequence of the given CSV. */ public function getInputBOM(): string { if (null !== $this->input_bom) { return $this->input_bom; } $this->document->setFlags(SplFileObject::READ_CSV); $this->document->rewind(); $this->input_bom = Info::fetchBOMSequence((string) $this->document->fread(4)) ?? ''; return $this->input_bom; } /** * Tells whether the stream filter read capabilities can be used. */ public function supportsStreamFilterOnRead(): bool { return $this->document instanceof Stream && (static::STREAM_FILTER_MODE & STREAM_FILTER_READ) === STREAM_FILTER_READ; } /** * Tells whether the stream filter write capabilities can be used. */ public function supportsStreamFilterOnWrite(): bool { return $this->document instanceof Stream && (static::STREAM_FILTER_MODE & STREAM_FILTER_WRITE) === STREAM_FILTER_WRITE; } /** * Tells whether the specified stream filter is attached to the current stream. */ public function hasStreamFilter(string $filtername): bool { return $this->stream_filters[$filtername] ?? false; } /** * Tells whether the BOM can be stripped if presents. */ public function isInputBOMIncluded(): bool { return $this->is_input_bom_included; } /** * Returns the CSV document as a Generator of string chunk. * * @throws Exception if the number of bytes is less than 1 */ public function chunk(int $length): Generator { if ($length < 1) { throw InvalidArgument::dueToInvalidChunkSize($length, __METHOD__); } $this->document->rewind(); $this->document->setFlags(0); if (-1 === $this->document->fseek(strlen($this->getInputBOM()))) { throw new RuntimeException('Unable to seek the document.'); } yield from str_split($this->output_bom.$this->document->fread($length), $length); while ($this->document->valid()) { yield $this->document->fread($length); } } /** * Retrieves the CSV content. * * @throws Exception If the string representation can not be returned */ public function toString(): string { $raw = ''; foreach ($this->chunk(8192) as $chunk) { $raw .= $chunk; } return $raw; } /** * Outputs all data on the CSV file. * * Returns the number of characters read from the handle and passed through to the output. * * @throws Exception */ public function output(string $filename = null): int { if (null !== $filename) { $this->sendHeaders($filename); } $this->document->rewind(); $this->document->setFlags(0); if (!$this->is_input_bom_included && -1 === $this->document->fseek(strlen($this->getInputBOM()))) { throw new RuntimeException('Unable to seek the document.'); } $stream = Stream::createFromString($this->output_bom); $stream->rewind(); $res1 = $stream->fpassthru(); if (false === $res1) { throw new RuntimeException('Unable to output the document.'); } $res2 = $this->document->fpassthru(); if (false === $res2) { throw new RuntimeException('Unable to output the document.'); } return $res1 + $res2; } /** * Send the CSV headers. * * Adapted from Symfony\Component\HttpFoundation\ResponseHeaderBag::makeDisposition * * @throws Exception if the submitted header is invalid according to RFC 6266 * * @see https://tools.ietf.org/html/rfc6266#section-4.3 */ protected function sendHeaders(string $filename): void { if (strlen($filename) !== strcspn($filename, '\\/')) { throw InvalidArgument::dueToInvalidHeaderFilename($filename); } $flag = FILTER_FLAG_STRIP_LOW; if (strlen($filename) !== mb_strlen($filename)) { $flag |= FILTER_FLAG_STRIP_HIGH; } /** @var string $filtered_name */ $filtered_name = filter_var($filename, FILTER_UNSAFE_RAW, $flag); $filename_fallback = str_replace('%', '', $filtered_name); $disposition = sprintf('attachment; filename="%s"', str_replace('"', '\\"', $filename_fallback)); if ($filename !== $filename_fallback) { $disposition .= sprintf("; filename*=utf-8''%s", rawurlencode($filename)); } header('Content-Type: text/csv'); header('Content-Transfer-Encoding: binary'); header('Content-Description: File Transfer'); header('Content-Disposition: '.$disposition); } /** * Sets the field delimiter. * * @throws InvalidArgument If the Csv control character is not one character only. */ public function setDelimiter(string $delimiter): static { if ($delimiter === $this->delimiter) { return $this; } if (1 !== strlen($delimiter)) { throw InvalidArgument::dueToInvalidDelimiterCharacter($delimiter, __METHOD__); } $this->delimiter = $delimiter; $this->resetProperties(); return $this; } /** * Sets the field enclosure. * * @throws InvalidArgument If the Csv control character is not one character only. */ public function setEnclosure(string $enclosure): static { if ($enclosure === $this->enclosure) { return $this; } if (1 !== strlen($enclosure)) { throw InvalidArgument::dueToInvalidEnclosureCharacter($enclosure, __METHOD__); } $this->enclosure = $enclosure; $this->resetProperties(); return $this; } /** * Sets the field escape character. * * @throws InvalidArgument If the Csv control character is not one character only. */ public function setEscape(string $escape): static { if ($escape === $this->escape) { return $this; } if ('' !== $escape && 1 !== strlen($escape)) { throw InvalidArgument::dueToInvalidEscapeCharacter($escape, __METHOD__); } $this->escape = $escape; $this->resetProperties(); return $this; } /** * Enables BOM Stripping. */ public function skipInputBOM(): static { $this->is_input_bom_included = false; return $this; } /** * Disables skipping Input BOM. */ public function includeInputBOM(): static { $this->is_input_bom_included = true; return $this; } /** * Sets the BOM sequence to prepend the CSV on output. */ public function setOutputBOM(string $str): static { $this->output_bom = $str; return $this; } /** * Append a stream filter. * * @throws InvalidArgument If the stream filter API can not be appended * @throws UnavailableFeature If the stream filter API can not be used */ public function addStreamFilter(string $filtername, null|array $params = null): static { if (!$this->document instanceof Stream) { throw UnavailableFeature::dueToUnsupportedStreamFilterApi(get_class($this->document)); } $this->document->appendFilter($filtername, static::STREAM_FILTER_MODE, $params); $this->stream_filters[$filtername] = true; $this->resetProperties(); $this->input_bom = null; return $this; } /** * DEPRECATION WARNING! This method will be removed in the next major point release. * * @deprecated since version 9.7.0 * @see AbstractCsv::supportsStreamFilterOnRead * @see AbstractCsv::supportsStreamFilterOnWrite * @codeCoverageIgnore * * Returns the stream filter mode. */ public function getStreamFilterMode(): int { return static::STREAM_FILTER_MODE; } /** * DEPRECATION WARNING! This method will be removed in the next major point release. * * @deprecated since version 9.7.0 * @see AbstractCsv::supportsStreamFilterOnRead * @see AbstractCsv::supportsStreamFilterOnWrite * @codeCoverageIgnore * * Tells whether the stream filter capabilities can be used. */ public function supportsStreamFilter(): bool { return $this->document instanceof Stream; } /** * Retrieves the CSV content. * * DEPRECATION WARNING! This method will be removed in the next major point release * * @deprecated since version 9.7.0 * @see AbstractCsv::toString * @codeCoverageIgnore */ public function getContent(): string { return $this->toString(); } /** * DEPRECATION WARNING! This method will be removed in the next major point release. * * @deprecated since version 9.1.0 * @see AbstractCsv::toString * @codeCoverageIgnore * * Retrieves the CSV content */ public function __toString(): string { return $this->toString(); } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use DOMDocument; use DOMElement; use DOMException; use function preg_match; /** * Converts tabular data into an HTML Table string. */ class HTMLConverter { /** table class attribute value. */ protected string $class_name = 'table-csv-data'; /** table id attribute value. */ protected string $id_value = ''; protected XMLConverter $xml_converter; public static function create(): self { return new self(); } /** * DEPRECATION WARNING! This method will be removed in the next major point release. * * @throws DOMException * @see HTMLConverterTest::create() * @deprecated since version 9.7.0 */ public function __construct() { $this->xml_converter = XMLConverter::create() ->rootElement('table') ->recordElement('tr') ->fieldElement('td') ; } /** * Converts a tabular data collection into an HTML table string. * * @param array<string> $header_record An optional array of headers outputted using the `<thead>` and `<th>` elements * @param array<string> $footer_record An optional array of footers outputted using the `<tfoot>` and `<th>` elements */ public function convert(iterable $records, array $header_record = [], array $footer_record = []): string { $doc = new DOMDocument('1.0'); if ([] === $header_record && [] === $footer_record) { $table = $this->xml_converter->import($records, $doc); $this->addHTMLAttributes($table); $doc->appendChild($table); /** @var string $content */ $content = $doc->saveHTML(); return $content; } $table = $doc->createElement('table'); $this->addHTMLAttributes($table); $this->appendHeaderSection('thead', $header_record, $table); $this->appendHeaderSection('tfoot', $footer_record, $table); $table->appendChild($this->xml_converter->rootElement('tbody')->import($records, $doc)); $doc->appendChild($table); return (string) $doc->saveHTML(); } /** * Creates a DOMElement representing an HTML table heading section. * * @throws DOMException */ protected function appendHeaderSection(string $node_name, array $record, DOMElement $table): void { if ([] === $record) { return; } /** @var DOMDocument $ownerDocument */ $ownerDocument = $table->ownerDocument; $node = $this->xml_converter ->rootElement($node_name) ->recordElement('tr') ->fieldElement('th') ->import([$record], $ownerDocument) ; /** @var DOMElement $element */ foreach ($node->getElementsByTagName('th') as $element) { $element->setAttribute('scope', 'col'); } $table->appendChild($node); } /** * Adds class and id attributes to an HTML tag. */ protected function addHTMLAttributes(DOMElement $node): void { $node->setAttribute('class', $this->class_name); $node->setAttribute('id', $this->id_value); } /** * HTML table class name setter. * * @throws DOMException if the id_value contains any type of whitespace */ public function table(string $class_name, string $id_value = ''): self { if (1 === preg_match(",\s,", $id_value)) { throw new DOMException("The id attribute's value must not contain whitespace (spaces, tabs etc.)"); } $clone = clone $this; $clone->class_name = $class_name; $clone->id_value = $id_value; return $clone; } /** * HTML tr record offset attribute setter. */ public function tr(string $record_offset_attribute_name): self { $clone = clone $this; $clone->xml_converter = $this->xml_converter->recordElement('tr', $record_offset_attribute_name); return $clone; } /** * HTML td field name attribute setter. */ public function td(string $fieldname_attribute_name): self { $clone = clone $this; $clone->xml_converter = $this->xml_converter->fieldElement('td', $fieldname_attribute_name); return $clone; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use InvalidArgumentException; use Stringable; use function array_fill_keys; use function array_keys; use function array_map; use function is_string; /** * A Formatter to tackle CSV Formula Injection. * * @see http://georgemauer.net/2017/10/07/csv-injection.html */ class EscapeFormula { /** Spreadsheet formula starting character. */ public const FORMULA_STARTING_CHARS = ['=', '-', '+', '@', "\t", "\r"]; /** Effective Spreadsheet formula starting characters. */ protected array $special_chars = []; /** * @param string $escape escape character to escape each CSV formula field * @param array<string> $special_chars additional spreadsheet formula starting characters */ public function __construct( protected string $escape = "'", array $special_chars = [] ) { $this->special_chars = array_fill_keys([ ...self::FORMULA_STARTING_CHARS, ...$this->filterSpecialCharacters(...$special_chars), ], 1); } /** * Filter submitted special characters. * * @throws InvalidArgumentException if the string is not a single character * * @return array<string> */ protected function filterSpecialCharacters(string ...$characters): array { foreach ($characters as $str) { if (1 !== strlen($str)) { throw new InvalidArgumentException('The submitted string '.$str.' must be a single character'); } } return $characters; } /** * Returns the list of character the instance will escape. * * @return array<string> */ public function getSpecialCharacters(): array { return array_keys($this->special_chars); } /** * Returns the escape character. */ public function getEscape(): string { return $this->escape; } /** * Escapes a CSV record. */ public function escapeRecord(array $record): array { return array_map($this->escapeField(...), $record); } public function unescapeRecord(array $record): array { return array_map($this->unescapeField(...), $record); } /** * Escapes a CSV cell if its content is stringable. */ protected function escapeField(mixed $cell): mixed { $strOrNull = match (true) { is_string($cell) => $cell, $cell instanceof Stringable => (string) $cell, default => null, }; return match (true) { null == $strOrNull, !isset($strOrNull[0], $this->special_chars[$strOrNull[0]]) => $cell, default => $this->escape.$strOrNull, }; } protected function unescapeField(mixed $cell): mixed { $strOrNull = match (true) { is_string($cell) => $cell, $cell instanceof Stringable => (string) $cell, default => null, }; return match (true) { null === $strOrNull, !isset($strOrNull[0], $strOrNull[1]), $strOrNull[0] !== $this->escape, !isset($this->special_chars[$strOrNull[1]]) => $cell, default => substr($strOrNull, 1), }; } /** * @deprecated since 9.7.2 will be removed in the next major release * @codeCoverageIgnore * * Tells whether the submitted value is stringable. * * @param mixed $value value to check if it is stringable */ protected function isStringable(mixed $value): bool { return is_string($value) || $value instanceof Stringable; } /** * @deprecated since 9.11.0 will be removed in the next major release * @codeCoverageIgnore * * League CSV formatter hook. * * @see escapeRecord */ public function __invoke(array $record): array { return $this->escapeRecord($record); } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv\Serializer; use Throwable; interface SerializationFailed extends Throwable { } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv\Serializer; use ReflectionParameter; use ReflectionProperty; use function filter_var; /** * @implements TypeCasting<?bool> */ final class CastToBool implements TypeCasting { private readonly bool $isNullable; private readonly Type $type; private ?bool $default = null; public function __construct(ReflectionProperty|ReflectionParameter $reflectionProperty) { [$this->type, $this->isNullable] = $this->init($reflectionProperty); } public function setOptions(bool $default = null): void { $this->default = $default; } /** * @throws TypeCastingFailed */ public function toVariable(?string $value): ?bool { $returnValue = match (true) { null !== $value => filter_var($value, Type::Bool->filterFlag()), $this->isNullable => $this->default, default => throw TypeCastingFailed::dueToNotNullableType('boolean'), }; return match (true) { Type::True->equals($this->type) && true !== $returnValue && !$this->isNullable, Type::False->equals($this->type) && false !== $returnValue && !$this->isNullable => throw TypeCastingFailed::dueToInvalidValue(match (true) { null === $value => 'null', '' === $value => 'empty string', default => $value, }, $this->type->value), default => $returnValue, }; } /** * @return array{0:Type, 1:bool} */ private function init(ReflectionProperty|ReflectionParameter $reflectionProperty): array { if (null === $reflectionProperty->getType()) { return [Type::Mixed, true]; } $type = null; $isNullable = false; foreach (Type::list($reflectionProperty) as $found) { if (!$isNullable && $found[1]->allowsNull()) { $isNullable = true; } if (null === $type && $found[0]->isOneOf(Type::Mixed, Type::Bool, Type::True, Type::False)) { $type = $found; } } if (null === $type) { throw MappingFailed::dueToTypeCastingUnsupportedType($reflectionProperty, $this, 'bool', 'mixed'); } return [$type[0], $isNullable]; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv\Serializer; use ReflectionParameter; use ReflectionProperty; use function filter_var; /** * @implements TypeCasting<?int> */ final class CastToInt implements TypeCasting { private readonly bool $isNullable; private ?int $default = null; public function __construct(ReflectionProperty|ReflectionParameter $reflectionProperty) { $this->isNullable = $this->init($reflectionProperty); } public function setOptions(?int $default = null): void { $this->default = $default; } /** * @throws TypeCastingFailed */ public function toVariable(?string $value): ?int { if (null === $value) { return match ($this->isNullable) { true => $this->default, false => throw TypeCastingFailed::dueToNotNullableType('integer'), }; } $int = filter_var($value, Type::Int->filterFlag()); return match ($int) { false => throw TypeCastingFailed::dueToInvalidValue($value, Type::Int->value), default => $int, }; } private function init(ReflectionProperty|ReflectionParameter $reflectionProperty): bool { if (null === $reflectionProperty->getType()) { return true; } $type = null; $isNullable = false; foreach (Type::list($reflectionProperty) as $found) { if (!$isNullable && $found[1]->allowsNull()) { $isNullable = true; } if (null === $type && $found[0]->isOneOf(Type::Mixed, Type::Int, Type::Float)) { $type = $found; } } if (null === $type) { throw throw MappingFailed::dueToTypeCastingUnsupportedType($reflectionProperty, $this, 'int', 'float', 'null', 'mixed'); } return $isNullable; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv\Serializer; use ReflectionParameter; use ReflectionProperty; use function filter_var; /** * @implements TypeCasting<?float> */ final class CastToFloat implements TypeCasting { private readonly bool $isNullable; private ?float $default = null; public function __construct(ReflectionProperty|ReflectionParameter $reflectionProperty) { $this->isNullable = $this->init($reflectionProperty); } public function setOptions(int|float|null $default = null): void { $this->default = $default; } /** * @throws TypeCastingFailed */ public function toVariable(?string $value): ?float { if (null === $value) { return match ($this->isNullable) { true => $this->default, false => throw TypeCastingFailed::dueToNotNullableType('float'), }; } $float = filter_var($value, Type::Float->filterFlag()); return match ($float) { false => throw TypeCastingFailed::dueToInvalidValue($value, Type::Float->value), default => $float, }; } private function init(ReflectionProperty|ReflectionParameter $reflectionProperty): bool { if (null === $reflectionProperty->getType()) { return true; } $type = null; $isNullable = false; foreach (Type::list($reflectionProperty) as $found) { if (!$isNullable && $found[1]->allowsNull()) { $isNullable = true; } if (null === $type && $found[0]->isOneOf(Type::Mixed, Type::Float)) { $type = $found; } } if (null === $type) { throw throw MappingFailed::dueToTypeCastingUnsupportedType($reflectionProperty, $this, 'float', 'null', 'mixed'); } return $isNullable; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv\Serializer; use ReflectionParameter; use ReflectionProperty; /** * @implements TypeCasting<?string> */ final class CastToString implements TypeCasting { private readonly bool $isNullable; private readonly Type $type; private ?string $default = null; public function __construct(ReflectionProperty|ReflectionParameter $reflectionProperty) { [$this->type, $this->isNullable] = $this->init($reflectionProperty); } public function setOptions(?string $default = null): void { $this->default = $default; } /** * @throws TypeCastingFailed */ public function toVariable(?string $value): ?string { $returnedValue = match(true) { null !== $value => $value, $this->isNullable => $this->default, default => throw TypeCastingFailed::dueToNotNullableType($this->type->value), }; return match (true) { Type::Null->equals($this->type) && null !== $returnedValue => throw TypeCastingFailed::dueToInvalidValue(match (true) { null === $value => 'null', '' === $value => 'empty string', default => $value, }, $this->type->value), default => $returnedValue, }; } /** * @return array{0:Type, 1:bool} */ private function init(ReflectionProperty|ReflectionParameter $reflectionProperty): array { if (null === $reflectionProperty->getType()) { return [Type::Mixed, true]; } $type = null; $isNullable = false; foreach (Type::list($reflectionProperty) as $found) { if (!$isNullable && $found[1]->allowsNull()) { $isNullable = true; } if (null === $type && $found[0]->isOneOf(Type::String, Type::Mixed, Type::Null)) { $type = $found; } } if (null === $type) { throw throw MappingFailed::dueToTypeCastingUnsupportedType($reflectionProperty, $this, 'string', 'mixed', 'null'); } return [$type[0], $isNullable]; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv\Serializer; use Attribute; #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY)] final class MapCell { /** * @param class-string|string|null $cast */ public function __construct( public readonly string|int|null $column = null, public readonly ?string $cast = null, public readonly array $options = [], public readonly bool $ignore = false, ) { } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv\Serializer; use Attribute; #[Attribute(Attribute::TARGET_CLASS)] final class AfterMapping { /** @var array<string> $methods */ public readonly array $methods; public function __construct(string ...$methods) { $this->methods = $methods; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv\Serializer; /** * @template TValue */ interface TypeCasting { /** * @throws TypeCastingFailed * * @return TValue */ public function toVariable(?string $value): mixed; /** * Accepts additional parameters to configure the class * Parameters should be scalar value, null or array containing * only scalar value and null. * * @throws MappingFailed */ public function setOptions(): void; } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv\Serializer; use ReflectionException; use ReflectionMethod; use ReflectionProperty; /** * @internal */ final class PropertySetter { public function __construct( private readonly ReflectionMethod|ReflectionProperty $accessor, public readonly int $offset, private readonly TypeCasting $cast, ) { } /** * @throws ReflectionException */ public function __invoke(object $object, ?string $value): void { $typeCastedValue = $this->cast->toVariable($value); match (true) { $this->accessor instanceof ReflectionMethod => $this->accessor->invoke($object, $typeCastedValue), $this->accessor instanceof ReflectionProperty => $this->accessor->setValue($object, $typeCastedValue), }; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv\Serializer; use JsonException; use ReflectionParameter; use ReflectionProperty; use function explode; use function is_array; use function json_decode; use function str_getcsv; use function strlen; use const FILTER_REQUIRE_ARRAY; use const JSON_THROW_ON_ERROR; /** * @implements TypeCasting<?array> */ final class CastToArray implements TypeCasting { private ArrayShape $shape; private readonly Type $type; private readonly bool $isNullable; private int $filterFlag; /** @var non-empty-string */ private string $separator = ','; private string $delimiter = ''; private string $enclosure = '"'; /** @var int<1, max> $depth */ private int $depth = 512; private int $flags = 0; private ?array $default = null; /** * @throws MappingFailed */ public function __construct(ReflectionProperty|ReflectionParameter $reflectionProperty) { [$this->type, $this->isNullable] = $this->init($reflectionProperty); } /** * @param non-empty-string $delimiter * @param non-empty-string $separator * @param int<1, max> $depth * * @throws MappingFailed */ public function setOptions( ?array $default = null, ArrayShape|string $shape = ArrayShape::List, string $separator = ',', string $delimiter = ',', string $enclosure = '"', int $depth = 512, int $flags = 0, Type|string $type = Type::String, ): void { if (!$shape instanceof ArrayShape) { $shape = ArrayShape::tryFrom($shape) ?? throw new MappingFailed('Unable to resolve the array shape; Verify your options arguments.'); } if (!$type instanceof Type) { $type = Type::tryFrom($type) ?? throw new MappingFailed('Unable to resolve the array value type; Verify your options arguments.'); } $this->shape = $shape; $this->depth = $depth; $this->separator = $separator; $this->delimiter = $delimiter; $this->enclosure = $enclosure; $this->flags = $flags; $this->default = $default; $this->filterFlag = match (true) { 1 > $this->depth && $this->shape->equals(ArrayShape::Json) => throw new MappingFailed('the json depth can not be less than 1.'), 1 > strlen($this->separator) && $this->shape->equals(ArrayShape::List) => throw new MappingFailed('expects separator to be a non-empty string for list conversion; empty string given.'), 1 !== strlen($this->delimiter) && $this->shape->equals(ArrayShape::Csv) => throw new MappingFailed('expects delimiter to be a single character for CSV conversion; `'.$this->delimiter.'` given.'), 1 !== strlen($this->enclosure) && $this->shape->equals(ArrayShape::Csv) => throw new MappingFailed('expects enclosure to be a single character; `'.$this->enclosure.'` given.'), default => $this->resolveFilterFlag($type), }; } public function toVariable(?string $value): ?array { if (null === $value) { return match (true) { $this->isNullable, Type::Mixed->equals($this->type) => $this->default, default => throw TypeCastingFailed::dueToNotNullableType($this->type->value), }; } if ('' === $value) { return []; } try { $result = match ($this->shape) { ArrayShape::Json => json_decode($value, true, $this->depth, $this->flags | JSON_THROW_ON_ERROR), ArrayShape::List => filter_var(explode($this->separator, $value), $this->filterFlag, FILTER_REQUIRE_ARRAY), ArrayShape::Csv => filter_var(str_getcsv($value, $this->delimiter, $this->enclosure, ''), $this->filterFlag, FILTER_REQUIRE_ARRAY), }; if (!is_array($result)) { throw TypeCastingFailed::dueToInvalidValue($value, $this->type->value); } return $result; } catch (JsonException $exception) { throw TypeCastingFailed::dueToInvalidValue($value, $this->type->value, $exception); } } /** * @throws MappingFailed if the type is not supported */ private function resolveFilterFlag(?Type $type): int { return match (true) { $this->shape->equals(ArrayShape::Json) => Type::String->filterFlag(), $type instanceof Type && $type->isOneOf(Type::Bool, Type::True, Type::False, Type::String, Type::Float, Type::Int) => $type->filterFlag(), default => throw new MappingFailed('Only scalar type are supported for `array` value casting.'), }; } /** * @return array{0:Type, 1:bool} */ private function init(ReflectionProperty|ReflectionParameter $reflectionProperty): array { if (null === $reflectionProperty->getType()) { return [Type::Mixed, true]; } $type = null; $isNullable = false; foreach (Type::list($reflectionProperty) as $found) { if (!$isNullable && $found[1]->allowsNull()) { $isNullable = true; } if (null === $type && $found[0]->isOneOf(Type::Mixed, Type::Array, Type::Iterable)) { $type = $found; } } if (null === $type) { throw MappingFailed::dueToTypeCastingUnsupportedType($reflectionProperty, $this, 'array', 'iterable', 'mixed'); } return [$type[0], $isNullable]; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\Csv\Serializer; use function in_array; enum ArrayShape: string { case List = 'list'; case Csv = 'csv'; case Json = 'json'; public function equals(mixed $value): bool { return $value instanceof self && $value === $this; } public function isOneOf(self ...$types): bool { return in_array($this, $types, true); } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv\Serializer; use LogicException; use ReflectionParameter; use ReflectionProperty; use Throwable; final class MappingFailed extends LogicException implements SerializationFailed { public static function dueToUnsupportedType(ReflectionProperty|ReflectionParameter $reflectionProperty): self { $suffix = 'is missing or is not supported.'; return new self(match (true) { $reflectionProperty instanceof ReflectionParameter => 'The type for the method `'.$reflectionProperty->getDeclaringClass()?->getName().'::'.$reflectionProperty->getDeclaringFunction()->getName().'` first argument `'.$reflectionProperty->getName().'` '.$suffix, $reflectionProperty instanceof ReflectionProperty => 'The property type for `'.$reflectionProperty->getDeclaringClass()->getName().'::'.$reflectionProperty->getName().'` '.$suffix, }); } public static function dueToTypeCastingUnsupportedType( ReflectionProperty|ReflectionParameter $reflectionProperty, TypeCasting $typeCasting, string ...$types ): self { $suffix = 'is invalid; `'.implode('` or `', $types).'` type must be used with the `'.$typeCasting::class.'`.'; return new self(match (true) { $reflectionProperty instanceof ReflectionParameter => 'The type for the method `'.$reflectionProperty->getDeclaringClass()?->getName().'::'.$reflectionProperty->getDeclaringFunction()->getName().'` first argument `'.$reflectionProperty->getName().'` '.$suffix, $reflectionProperty instanceof ReflectionProperty => 'The property type for `'.$reflectionProperty->getDeclaringClass()->getName().'::'.$reflectionProperty->getName().'` '.$suffix, }); } public static function dueToInvalidCastingArguments(Throwable $exception = null): self { return new self('Unable to load the casting mechanism. Please verify your casting arguments', 0, $exception); } public static function dueToInvalidTypeCastingClass(string $typeCaster): self { return new self('`'.$typeCaster.'` must be an resolvable class implementing the `'.TypeCasting::class.'` interface or a supported alias.'); } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv\Serializer; use Closure; use Iterator; use League\Csv\MapIterator; use ReflectionAttribute; use ReflectionClass; use ReflectionException; use ReflectionMethod; use ReflectionParameter; use ReflectionProperty; use Throwable; use function array_search; use function array_values; use function count; use function is_int; final class Denormalizer { private static bool $emptyStringAsNull = true; private readonly ReflectionClass $class; /** @var array<ReflectionProperty> */ private readonly array $properties; /** @var array<PropertySetter> */ private readonly array $propertySetters; /** @var array<ReflectionMethod> */ private readonly array $postMapCalls; /** * @param class-string $className * @param array<string> $propertyNames * * @throws MappingFailed */ public function __construct(string $className, array $propertyNames = []) { $this->class = $this->setClass($className); $this->properties = $this->class->getProperties(); $this->propertySetters = $this->setPropertySetters($propertyNames); $this->postMapCalls = $this->setPostMapCalls(); } public static function allowEmptyStringAsNull(): void { self::$emptyStringAsNull = true; } public static function disallowEmptyStringAsNull(): void { self::$emptyStringAsNull = false; } /** * @throws MappingFailed */ public static function registerType(string $type, Closure $callback): void { CallbackCasting::register($type, $callback); } public static function unregisterType(string $type): bool { return CallbackCasting::unregisterType($type); } public static function unregisterAllTypes(): void { CallbackCasting::unregisterTypes(); } /** * @throws MappingFailed */ public static function registerAlias(string $alias, string $type, Closure $callback): void { CallbackCasting::register($type, $callback, $alias); } public static function unregisterAlias(string $alias): bool { return CallbackCasting::unregisterAlias($alias); } public static function unregisterAllAliases(): void { CallbackCasting::unregisterAliases(); } public static function unregisterAll(): void { CallbackCasting::unregisterAll(); } /** * @return array<string> */ public static function types(): array { $default = [...array_column(Type::cases(), 'value'), ...CallbackCasting::types()]; return array_values(array_unique($default)); } /** * @return array<string, string> */ public static function aliases(): array { return CallbackCasting::aliases(); } public static function supportsAlias(string $alias): bool { return CallbackCasting::supportsAlias($alias); } /** * @param class-string $className * @param array<?string> $record * * @throws DenormalizationFailed * @throws MappingFailed * @throws ReflectionException * @throws TypeCastingFailed */ public static function assign(string $className, array $record): object { return (new self($className, array_keys($record)))->denormalize($record); } /** * @param class-string $className * @param array<string> $propertyNames * * @throws MappingFailed * @throws TypeCastingFailed */ public static function assignAll(string $className, iterable $records, array $propertyNames = []): Iterator { return (new self($className, $propertyNames))->denormalizeAll($records); } public function denormalizeAll(iterable $records): Iterator { $check = true; $assign = function (array $record) use (&$check) { $object = $this->class->newInstanceWithoutConstructor(); $this->hydrate($object, $record); if ($check) { $check = false; $this->assertObjectIsInValidState($object); } foreach ($this->postMapCalls as $accessor) { $accessor->invoke($object); } return $object; }; return MapIterator::fromIterable($records, $assign); } /** * @throws DenormalizationFailed * @throws ReflectionException * @throws TypeCastingFailed */ public function denormalize(array $record): object { $object = $this->class->newInstanceWithoutConstructor(); $this->hydrate($object, $record); $this->assertObjectIsInValidState($object); foreach ($this->postMapCalls as $accessor) { $accessor->invoke($object); } return $object; } /** * @param array<?string> $record * * @throws ReflectionException * @throws TypeCastingFailed */ private function hydrate(object $object, array $record): void { $record = array_values($record); foreach ($this->propertySetters as $propertySetter) { $value = $record[$propertySetter->offset]; if (is_string($value) && '' === trim($value) && self::$emptyStringAsNull) { $value = null; } $propertySetter($object, $value); } } /** * @throws DenormalizationFailed */ private function assertObjectIsInValidState(object $object): void { foreach ($this->properties as $property) { if (!$property->isInitialized($object)) { throw DenormalizationFailed::dueToUninitializedProperty($property); } } } /** * @param class-string $className * * @throws MappingFailed */ private function setClass(string $className): ReflectionClass { if (!class_exists($className)) { throw new MappingFailed('The class `'.$className.'` can not be denormalized; The class does not exist or could not be found.'); } $class = new ReflectionClass($className); if ($class->isInternal() && $class->isFinal()) { throw new MappingFailed('The class `'.$className.'` can not be denormalized; PHP internal class marked as final can not be instantiated without using the constructor.'); } return $class; } /** * @param array<string> $propertyNames * * @throws MappingFailed * * @return array<PropertySetter> */ private function setPropertySetters(array $propertyNames): array { $propertySetters = []; $methodNames = array_map(fn (string|int $propertyName) => is_int($propertyName) ? null : 'set'.ucfirst($propertyName), $propertyNames); foreach ([...$this->properties, ...$this->class->getMethods()] as $accessor) { $attributes = $accessor->getAttributes(MapCell::class, ReflectionAttribute::IS_INSTANCEOF); $propertySetter = match (count($attributes)) { 0 => $this->autoDiscoverPropertySetter($accessor, $propertyNames, $methodNames), 1 => $this->findPropertySetter($attributes[0]->newInstance(), $accessor, $propertyNames), default => throw new MappingFailed('Using more than one `'.MapCell::class.'` attribute on a class property or method is not supported.'), }; if (null !== $propertySetter) { $propertySetters[] = $propertySetter; } } return match ([]) { $propertySetters => throw new MappingFailed('No property or method from `'.$this->class->getName().'` could be used for denormalization.'), default => $propertySetters, }; } private function setPostMapCalls(): array { $methods = []; $attributes = $this->class->getAttributes(AfterMapping::class, ReflectionAttribute::IS_INSTANCEOF); $nbAttributes = count($attributes); if (0 === $nbAttributes) { return $methods; } if (1 < $nbAttributes) { throw new MappingFailed('Using more than one `'.AfterMapping::class.'` attribute on a class property or method is not supported.'); } /** @var AfterMapping $postMap */ $postMap = $attributes[0]->newInstance(); foreach ($postMap->methods as $method) { try { $accessor = $this->class->getMethod($method); } catch (ReflectionException $exception) { throw new MappingFailed('The method `'.$method.'` is not defined on the `'.$this->class->getName().'` class.', 0, $exception); } if (0 !== $accessor->getNumberOfRequiredParameters()) { throw new MappingFailed('The method `'.$this->class->getName().'::'.$accessor->getName().'` has too many required parameters.'); } $methods[] = $accessor; } return $methods; } /** * @param array<string> $propertyNames * @param array<?string> $methodNames * * @throws MappingFailed */ private function autoDiscoverPropertySetter(ReflectionMethod|ReflectionProperty $accessor, array $propertyNames, array $methodNames): ?PropertySetter { if ($accessor->isStatic() || !$accessor->isPublic()) { return null; } if ($accessor instanceof ReflectionMethod) { if ($accessor->isConstructor()) { return null; } if ([] === $accessor->getParameters()) { return null; } if (1 < $accessor->getNumberOfRequiredParameters()) { return null; } } /** @var int|false $offset */ /** @var ReflectionParameter|ReflectionProperty $reflectionProperty */ [$offset, $reflectionProperty] = match (true) { $accessor instanceof ReflectionMethod => [array_search($accessor->getName(), $methodNames, true), $accessor->getParameters()[0]], $accessor instanceof ReflectionProperty => [array_search($accessor->getName(), $propertyNames, true), $accessor], }; return match (true) { false === $offset, null === $reflectionProperty->getType() => null, default => new PropertySetter( $accessor, $offset, $this->resolveTypeCasting($reflectionProperty) ), }; } /** * @param array<string> $propertyNames * * @throws MappingFailed */ private function findPropertySetter(MapCell $cell, ReflectionMethod|ReflectionProperty $accessor, array $propertyNames): ?PropertySetter { if ($cell->ignore) { return null; } $typeCaster = $this->resolveTypeCaster($cell, $accessor); $offset = $cell->column ?? match (true) { $accessor instanceof ReflectionMethod => $this->getMethodFirstArgument($accessor)->getName(), $accessor instanceof ReflectionProperty => $accessor->getName(), }; if (!is_int($offset)) { if ([] === $propertyNames) { throw new MappingFailed('offset as string are only supported if the property names list is not empty.'); } /** @var int<0, max>|false $index */ $index = array_search($offset, $propertyNames, true); if (false === $index) { throw new MappingFailed('The `'.$offset.'` property could not be found in the property names list; Please verify your property names list.'); } $offset = $index; } $reflectionProperty = match (true) { $accessor instanceof ReflectionMethod => $accessor->getParameters()[0], $accessor instanceof ReflectionProperty => $accessor, }; return match (true) { 0 > $offset => throw new MappingFailed('offset integer position can only be positive or equals to 0; received `'.$offset.'`'), [] !== $propertyNames && $offset > count($propertyNames) - 1 => throw new MappingFailed('offset integer position can not exceed property names count.'), null === $typeCaster => new PropertySetter($accessor, $offset, $this->resolveTypeCasting($reflectionProperty, $cell->options)), default => new PropertySetter($accessor, $offset, $this->getTypeCasting($reflectionProperty, $typeCaster, $cell->options)), }; } /** * @throws MappingFailed */ private function getMethodFirstArgument(ReflectionMethod $reflectionMethod): ReflectionParameter { $arguments = $reflectionMethod->getParameters(); return match (true) { [] === $arguments => throw new MappingFailed('The method `'.$reflectionMethod->getDeclaringClass()->getName().'::'.$reflectionMethod->getName().'` does not use parameters.'), 1 < $reflectionMethod->getNumberOfRequiredParameters() => throw new MappingFailed('The method `'.$reflectionMethod->getDeclaringClass()->getName().'::'.$reflectionMethod->getName().'` has too many required parameters.'), default => $arguments[0] }; } /** * @throws MappingFailed */ private function getTypeCasting( ReflectionProperty|ReflectionParameter $reflectionProperty, string $typeCaster, array $options ): TypeCasting { try { if (str_starts_with($typeCaster, CallbackCasting::class.'@')) { $cast = new CallbackCasting($reflectionProperty, substr($typeCaster, strlen(CallbackCasting::class))); $cast->setOptions(...$options); return $cast; } /** @var TypeCasting $cast */ $cast = new $typeCaster($reflectionProperty); $cast->setOptions(...$options); return $cast; } catch (MappingFailed $exception) { throw $exception; } catch (Throwable $exception) { throw MappingFailed::dueToInvalidCastingArguments($exception); } } /** * @throws MappingFailed */ private function resolveTypeCasting(ReflectionProperty|ReflectionParameter $reflectionProperty, array $options = []): TypeCasting { $castResolver = function (ReflectionProperty|ReflectionParameter $reflectionProperty, $options): CallbackCasting { $cast = new CallbackCasting($reflectionProperty); $cast->setOptions(...$options); return $cast; }; try { return match (true) { CallbackCasting::supports($reflectionProperty) => $castResolver($reflectionProperty, $options), default => Type::resolve($reflectionProperty, $options), }; } catch (MappingFailed $exception) { throw $exception; } catch (Throwable $exception) { throw MappingFailed::dueToInvalidCastingArguments($exception); } } public function resolveTypeCaster(MapCell $cell, ReflectionMethod|ReflectionProperty $accessor): ?string { /** @var ?class-string<TypeCasting> $typeCaster */ $typeCaster = $cell->cast; if (null === $typeCaster) { return null; } if (class_exists($typeCaster)) { if (!(new ReflectionClass($typeCaster))->implementsInterface(TypeCasting::class)) { throw MappingFailed::dueToInvalidTypeCastingClass($typeCaster); } return $typeCaster; } if ($accessor instanceof ReflectionMethod) { $accessor = $accessor->getParameters()[0]; } if (!CallbackCasting::supports($accessor, $typeCaster)) { throw MappingFailed::dueToInvalidTypeCastingClass($typeCaster); } return CallbackCasting::class.$typeCaster; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv\Serializer; use ReflectionProperty; use RuntimeException; final class DenormalizationFailed extends RuntimeException implements SerializationFailed { public static function dueToUninitializedProperty(ReflectionProperty $reflectionProperty): self { return new self('The property '.$reflectionProperty->getDeclaringClass()->getName().'::'.$reflectionProperty->getName().' is not initialized.'); } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv\Serializer; use BackedEnum; use ReflectionEnum; use ReflectionParameter; use ReflectionProperty; use Throwable; use UnitEnum; /** * @implements TypeCasting<BackedEnum|UnitEnum|null> */ class CastToEnum implements TypeCasting { private readonly bool $isNullable; private readonly Type $type; private ?UnitEnum $default = null; private readonly string $propertyName; /** @var class-string<UnitEnum|BackedEnum> */ private string $class; /** * @throws MappingFailed */ public function __construct(ReflectionProperty|ReflectionParameter $reflectionProperty) { [$this->type, $this->class, $this->isNullable] = $this->init($reflectionProperty); $this->propertyName = $reflectionProperty->getName(); } /** * @param ?class-string<UnitEnum|BackedEnum> $className * * * @throws MappingFailed */ public function setOptions(?string $default = null, ?string $className = null): void { if (Type::Mixed->equals($this->type) || in_array($this->class, [BackedEnum::class , UnitEnum::class], true)) { if (null === $className || !enum_exists($className)) { throw new MappingFailed('`'.$this->propertyName.'` type is `'.($this->class ?? 'mixed').'` but the specified class via the `$className` argument is invalid or could not be found.'); } $this->class = $className; } try { $this->default = (null !== $default) ? $this->cast($default) : $default; } catch (TypeCastingFailed $exception) { throw new MappingFailed(message:'The `default` option is invalid.', previous: $exception); } } /** * @throws TypeCastingFailed */ public function toVariable(?string $value): BackedEnum|UnitEnum|null { return match (true) { null !== $value => $this->cast($value), $this->isNullable => $this->default, default => throw TypeCastingFailed::dueToNotNullableType($this->class), }; } /** * @throws TypeCastingFailed */ private function cast(string $value): BackedEnum|UnitEnum { try { $enum = new ReflectionEnum($this->class); if (!$enum->isBacked()) { return $enum->getCase($value)->getValue(); } $backedValue = 'int' === $enum->getBackingType()->getName() ? filter_var($value, Type::Int->filterFlag()) : $value; return $this->class::from($backedValue); /* @phpstan-ignore-line */ } catch (Throwable $exception) { throw throw TypeCastingFailed::dueToInvalidValue($value, $this->class, $exception); } } /** * @return array{0:Type, 1:class-string<UnitEnum|BackedEnum>, 2:bool} */ private function init(ReflectionProperty|ReflectionParameter $reflectionProperty): array { if (null === $reflectionProperty->getType()) { return [Type::Mixed, UnitEnum::class, true]; } $type = null; $isNullable = false; foreach (Type::list($reflectionProperty) as $found) { if (!$isNullable && $found[1]->allowsNull()) { $isNullable = true; } if (null === $type && $found[0]->isOneOf(Type::Mixed, Type::Enum)) { $type = $found; } } if (null === $type) { throw throw MappingFailed::dueToTypeCastingUnsupportedType($reflectionProperty, $this, 'enum', 'mixed'); } /** @var class-string<UnitEnum|BackedEnum> $className */ $className = $type[1]->getName(); return [$type[0], $className, $isNullable]; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv\Serializer; use RuntimeException; use Throwable; final class TypeCastingFailed extends RuntimeException implements SerializationFailed { public static function dueToNotNullableType(string $type, Throwable $exception = null): self { return new self('The `null` value can not be cast to an `'.$type.'`; the property type is not nullable.', 0, $exception); } public static function dueToInvalidValue(string $value, string $type, Throwable $previous = null): self { return new self('Unable to cast the given data `'.$value.'` to a `'.$type.'`.', 0, $previous); } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv\Serializer; use DateTime; use DateTimeImmutable; use DateTimeInterface; use DateTimeZone; use ReflectionClass; use ReflectionParameter; use ReflectionProperty; use Throwable; use function class_exists; use function is_string; /** * @implements TypeCasting<DateTimeImmutable|DateTime|null> */ final class CastToDate implements TypeCasting { /** @var class-string */ private string $class; private readonly bool $isNullable; private DateTimeImmutable|DateTime|null $default = null; private readonly Type $type; private readonly string $propertyName; private ?DateTimeZone $timezone = null; private ?string $format = null; /** * @throws MappingFailed */ public function __construct( ReflectionProperty|ReflectionParameter $reflectionProperty, ) { [$this->type, $this->class, $this->isNullable] = $this->init($reflectionProperty); $this->propertyName = $reflectionProperty->getName(); } /** * @param ?class-string $className * * @throws MappingFailed */ public function setOptions( ?string $default = null, ?string $format = null, DateTimeZone|string|null $timezone = null, ?string $className = null ): void { $this->class = match (true) { !interface_exists($this->class) && !Type::Mixed->equals($this->type) => $this->class, DateTimeInterface::class === $this->class && null === $className => DateTimeImmutable::class, interface_exists($this->class) && null !== $className && class_exists($className) && (new ReflectionClass($className))->implementsInterface($this->class) => $className, default => throw new MappingFailed('`'.$this->propertyName.'` type is `'.($this->class ?? 'mixed').'` but the specified class via the `$className` argument is invalid or could not be found.'), }; try { $this->format = $format; $this->timezone = is_string($timezone) ? new DateTimeZone($timezone) : $timezone; $this->default = (null !== $default) ? $this->cast($default) : $default; } catch (Throwable $exception) { throw new MappingFailed('The `timezone` and/or `format` options used for `'.self::class.'` are invalud.', 0, $exception); } } /** * @throws TypeCastingFailed */ public function toVariable(?string $value): DateTimeImmutable|DateTime|null { return match (true) { null !== $value && '' !== $value => $this->cast($value), $this->isNullable => $this->default, default => throw TypeCastingFailed::dueToNotNullableType($this->class), }; } /** * @throws TypeCastingFailed */ private function cast(string $value): DateTimeImmutable|DateTime { try { $date = null !== $this->format ? ($this->class)::createFromFormat($this->format, $value, $this->timezone) : new ($this->class)($value, $this->timezone); if (false === $date) { throw TypeCastingFailed::dueToInvalidValue($value, $this->class); } } catch (Throwable $exception) { if ($exception instanceof TypeCastingFailed) { throw $exception; } throw TypeCastingFailed::dueToInvalidValue($value, $this->class, $exception); } return $date; } /** * @throws MappingFailed * * @return array{0:Type, 1:class-string<DateTimeInterface>, 2:bool} */ private function init(ReflectionProperty|ReflectionParameter $reflectionProperty): array { if (null === $reflectionProperty->getType()) { return [Type::Mixed, DateTimeInterface::class, true]; } $type = null; $isNullable = false; foreach (Type::list($reflectionProperty) as $found) { if (!$isNullable && $found[1]->allowsNull()) { $isNullable = true; } if (null === $type && $found[0]->isOneOf(Type::Mixed, Type::Date)) { $type = $found; } } if (null === $type) { throw throw MappingFailed::dueToTypeCastingUnsupportedType($reflectionProperty, $this, DateTimeInterface::class, 'mixed'); } /** @var class-string<DateTimeInterface> $className */ $className = $type[1]->getName(); return [$type[0], $className, $isNullable]; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\Csv\Serializer; use DateTimeInterface; use ReflectionClass; use ReflectionNamedType; use ReflectionParameter; use ReflectionProperty; use ReflectionType; use ReflectionUnionType; use Throwable; use UnitEnum; use function class_exists; use function enum_exists; use function in_array; use const FILTER_UNSAFE_RAW; use const FILTER_VALIDATE_BOOL; use const FILTER_VALIDATE_FLOAT; use const FILTER_VALIDATE_INT; enum Type: string { case Bool = 'bool'; case True = 'true'; case False = 'false'; case Null = 'null'; case Int = 'int'; case Float = 'float'; case String = 'string'; case Mixed = 'mixed'; case Array = 'array'; case Iterable = 'iterable'; case Enum = UnitEnum::class; case Date = DateTimeInterface::class; public function equals(mixed $value): bool { return $value instanceof self && $value === $this; } public function isOneOf(self ...$types): bool { return in_array($this, $types, true); } public function filterFlag(): int { return match ($this) { self::Bool, self::True, self::False => FILTER_VALIDATE_BOOL, self::Int => FILTER_VALIDATE_INT, self::Float => FILTER_VALIDATE_FLOAT, default => FILTER_UNSAFE_RAW, }; } public static function resolve(ReflectionProperty|ReflectionParameter $reflectionProperty, array $arguments = []): TypeCasting { try { $cast = match (self::tryFromAccessor($reflectionProperty)) { self::Mixed, self::Null, self::String => new CastToString($reflectionProperty), self::Iterable, self::Array => new CastToArray($reflectionProperty), self::False, self::True, self::Bool => new CastToBool($reflectionProperty), self::Float => new CastToFloat($reflectionProperty), self::Int => new CastToInt($reflectionProperty), self::Date => new CastToDate($reflectionProperty), self::Enum => new CastToEnum($reflectionProperty), null => throw MappingFailed::dueToUnsupportedType($reflectionProperty), }; $cast->setOptions(...$arguments); return $cast; } catch (MappingFailed $exception) { throw $exception; } catch (Throwable $exception) { throw MappingFailed::dueToInvalidCastingArguments($exception); } } /** * @return list<array{0:Type, 1: ReflectionNamedType}> */ public static function list(ReflectionParameter|ReflectionProperty $reflectionProperty): array { $reflectionType = $reflectionProperty->getType() ?? throw MappingFailed::dueToUnsupportedType($reflectionProperty); $foundTypes = static function (array $res, ReflectionType $reflectionType) { if (!$reflectionType instanceof ReflectionNamedType) { return $res; } $type = self::tryFromName($reflectionType->getName()); if (null !== $type) { $res[] = [$type, $reflectionType]; } return $res; }; return match (true) { $reflectionType instanceof ReflectionNamedType => $foundTypes([], $reflectionType), $reflectionType instanceof ReflectionUnionType => array_reduce($reflectionType->getTypes(), $foundTypes, []), default => [], }; } public static function tryFromName(string $propertyType): ?self { $interfaceExists = interface_exists($propertyType); return match (true) { enum_exists($propertyType), $interfaceExists && (new ReflectionClass($propertyType))->implementsInterface(UnitEnum::class) => self::Enum, $interfaceExists && (new ReflectionClass($propertyType))->implementsInterface(DateTimeInterface::class), class_exists($propertyType) && (new ReflectionClass($propertyType))->implementsInterface(DateTimeInterface::class) => self::Date, default => self::tryFrom($propertyType), }; } public static function tryFromAccessor(ReflectionProperty|ReflectionParameter $reflectionProperty): ?self { $type = $reflectionProperty->getType(); if (null === $type) { return Type::Mixed; } if ($type instanceof ReflectionNamedType) { return self::tryFromName($type->getName()); } if (!$type instanceof ReflectionUnionType) { return null; } foreach ($type->getTypes() as $innerType) { if (!$innerType instanceof ReflectionNamedType) { continue; } $result = self::tryFromName($innerType->getName()); if ($result instanceof self) { return $result; } } return null; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv\Serializer; use Closure; use ReflectionNamedType; use ReflectionParameter; use ReflectionProperty; use ReflectionType; use ReflectionUnionType; use Throwable; use function array_key_exists; use function class_exists; /** * @internal Container for registering Closure as type and/or type alias casting * @template TValue */ final class CallbackCasting implements TypeCasting { /** @var array<string, Closure(?string, bool, mixed...): mixed> */ private static array $types = []; /** @var array<string, array<string, Closure(?string, bool, mixed...): mixed>> */ private static array $aliases = []; private string $type; private readonly bool $isNullable; /** @var Closure(?string, bool, mixed...): mixed */ private Closure $callback; private array $options; private string $message; public function __construct( ReflectionProperty|ReflectionParameter $reflectionProperty, private readonly ?string $alias = null ) { [$this->type, $this->isNullable] = self::resolve($reflectionProperty); $this->message = match (true) { $reflectionProperty instanceof ReflectionParameter => 'The method `'.$reflectionProperty->getDeclaringClass()?->getName().'::'.$reflectionProperty->getDeclaringFunction()->getName().'` argument `'.$reflectionProperty->getName().'` must be typed with a supported type.', $reflectionProperty instanceof ReflectionProperty => 'The property `'.$reflectionProperty->getDeclaringClass()->getName().'::'.$reflectionProperty->getName().'` must be typed with a supported type.', }; $this->callback = fn (?string $value, bool $isNullable, mixed ...$arguments): ?string => $value; } /** * @throws MappingFailed */ public function setOptions(string $type = null, mixed ...$options): void { if (null === $this->alias) { if (Type::Mixed->value === $this->type && null !== $type) { $this->type = $type; } if (array_key_exists($this->type, self::$types)) { $this->callback = self::$types[$this->type]; $this->options = $options; return; } throw new MappingFailed($this->message); } if (Type::Mixed->value === $this->type) { $this->type = self::aliases()[$this->alias]; } /** @var Closure $callback */ $callback = self::$aliases[$this->type][$this->alias]; $this->callback = $callback; $this->options = $options; } /** * @return TValue */ public function toVariable(?string $value): mixed { try { return ($this->callback)($value, $this->isNullable, ...$this->options); } catch (Throwable $exception) { if ($exception instanceof TypeCastingFailed) { throw $exception; } if (null === $value) { throw TypeCastingFailed::dueToNotNullableType($this->type, $exception); } throw TypeCastingFailed::dueToInvalidValue(match (true) { '' === $value => 'empty string', default => $value, }, $this->type, $exception); } } /** * @param Closure(?string, bool, mixed...): TValue $callback */ public static function register(string $type, Closure $callback, string $alias = null): void { if (null === $alias) { self::$types[$type] = match (true) { class_exists($type), interface_exists($type), Type::tryFrom($type) instanceof Type => $callback, default => throw new MappingFailed('The `'.$type.'` could not be register.'), }; return; } if (1 !== preg_match('/^@\w+$/', $alias)) { throw new MappingFailed("The alias `$alias` is invalid. It must start with an `@` character and contain alphanumeric (letters, numbers, regardless of case) plus underscore (_)."); } foreach (self::$aliases as $aliases) { foreach ($aliases as $registeredAlias => $__) { if ($alias === $registeredAlias) { throw new MappingFailed("The alias `$alias` is already registered. Please choose another name."); } } } self::$aliases[$type][$alias] = match (true) { class_exists($type), interface_exists($type), Type::tryFrom($type) instanceof Type => $callback, default => throw new MappingFailed('The `'.$type.'` could not be register.'), }; } public static function unregisterType(string $type): bool { if (!array_key_exists($type, self::$types)) { return false; } unset(self::$types[$type]); return true; } public static function unregisterTypes(): void { self::$types = []; } public static function unregisterAlias(string $alias): bool { if (1 !== preg_match('/^@\w+$/', $alias)) { return false; } foreach (self::$aliases as $type => $aliases) { foreach ($aliases as $registeredAlias => $__) { if ($registeredAlias === $alias) { unset(self::$aliases[$type][$registeredAlias]); return true; } } } return false; } public static function unregisterAliases(): void { self::$aliases = []; } public static function unregisterAll(): void { self::$types = []; self::$aliases = []; } public static function supportsAlias(?string $alias): bool { return null !== $alias && array_key_exists($alias, self::aliases()); } public static function supportsType(?string $type): bool { return null !== $type && array_key_exists($type, self::$types); } /** * @return array<string> */ public static function types(): array { return array_keys(self::$types); } /** * @return array<string, string> */ public static function aliases(): array { $res = []; foreach (self::$aliases as $registeredType => $aliases) { foreach ($aliases as $registeredAlias => $__) { $res[$registeredAlias] = $registeredType; } } return $res; } public static function supports(ReflectionParameter|ReflectionProperty $reflectionProperty, string $alias = null): bool { $propertyTypeList = self::getTypes($reflectionProperty->getType()); if ([] === $propertyTypeList && self::supportsAlias($alias)) { return true; } foreach ($propertyTypeList as $propertyType) { $type = $propertyType->getName(); if (null === $alias) { if (array_key_exists($type, self::$types)) { return true; } continue; } if ((self::aliases()[$alias] ?? null) === $type || (Type::Mixed->value === $type && self::supportsAlias($alias))) { return true; } } return false; } /** * @throws MappingFailed * * @return array{0:string, 1:bool} */ private static function resolve(ReflectionParameter|ReflectionProperty $reflectionProperty): array { if (null === $reflectionProperty->getType()) { return [Type::Mixed->value, true]; } $types = self::getTypes($reflectionProperty->getType()); $type = null; $isNullable = false; $hasMixed = false; foreach ($types as $foundType) { if (!$isNullable && $foundType->allowsNull()) { $isNullable = true; } if (null === $type) { if ( array_key_exists($foundType->getName(), self::$types) || array_key_exists($foundType->getName(), self::$aliases) ) { $type = $foundType; } if (true !== $hasMixed && Type::Mixed->value === $foundType->getName()) { $hasMixed = true; } } } return match (true) { $type instanceof ReflectionNamedType => [$type->getName(), $isNullable], $hasMixed => [Type::Mixed->value, true], default => throw new MappingFailed(match (true) { $reflectionProperty instanceof ReflectionParameter => 'The method `'.$reflectionProperty->getDeclaringClass()?->getName().'::'.$reflectionProperty->getDeclaringFunction()->getName().'` argument `'.$reflectionProperty->getName().'` must be typed with a supported type.', $reflectionProperty instanceof ReflectionProperty => 'The property `'.$reflectionProperty->getDeclaringClass()->getName().'::'.$reflectionProperty->getName().'` must be typed with a supported type.', }), }; } /** * @return array<ReflectionNamedType> */ private static function getTypes(?ReflectionType $type): array { return match (true) { $type instanceof ReflectionNamedType => [$type], $type instanceof ReflectionUnionType => array_filter( $type->getTypes(), fn (ReflectionType $innerType) => $innerType instanceof ReflectionNamedType ), default => [], }; } /** * DEPRECATION WARNING! This method will be removed in the next major point release. * * @deprecated since version 9.13.0 * @see CallbackCasting::unregisterType() * @codeCoverageIgnore */ public static function unregister(string $type): bool { return self::unregisterType($type); } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use Closure; use Countable; use Iterator; use IteratorAggregate; /** * Represents a Tabular data. * * @method Iterator fetchColumnByName(string $name) returns a column from its name * @method Iterator fetchColumnByOffset(int $offset) returns a column from its offset * @method array first() returns the first record from the tabular data. * @method array nth(int $nth_record) returns the nth record from the tabular data. * @method mixed value(int|string $column = 0) returns a given value from the first element of the tabular data. * @method Iterator nthAsObject(int $nth, string $className, array $header = []) returns the nth record from the tabular data as an instance of the defined class name. * @method Iterator firstAsObject(string $className, array $header = []) returns the first record from the tabular data as an instance of the defined class name. * @method bool each(Closure $closure) iterates over each record and passes it to a closure. Iteration is interrupted if the closure returns false * @method bool exists(Closure $closure) tells whether at least one record satisfies the predicate. * @method mixed reduce(Closure $closure, mixed $initial = null) reduces the collection to a single value, passing the result of each iteration into the subsequent iteration * @method Iterator getObjects(string $className, array $header = []) Returns the tabular data records as an iterator object containing instance of the defined class name. * @method Iterator getRecordsAsObject(string $className, array $header = []) Returns the tabular data records as an iterator object containing instance of the defined class name. * @method TabularDataReader filter(Closure $closure) returns all the elements of this collection for which your callback function returns `true` * @method TabularDataReader slice(int $offset, int $length = null) extracts a slice of $length elements starting at position $offset from the Collection. * @method TabularDataReader sorted(Closure $orderBy) sorts the Collection according to the closure provided see Statement::orderBy method * @method TabularDataReader select(string|int ...$columnOffsetOrName) extract a selection of the tabular data records columns. * @method TabularDataReader matchingFirstOrFail(string $expression) extract the first found fragment identifier of the tabular data or fail * @method TabularDataReader|null matchingFirst(string $expression) extract the first found fragment identifier of the tabular data or return null if none is found * @method iterable<int, TabularDataReader> matching(string $expression) extract all found fragment identifiers for the tabular data * @method iterable<TabularDataReader> chunkBy(int $recordsCount) Chunk the TabulaDataReader into smaller TabularDataReader instances of the given size or less. * @method TabularDataReader mapHeader(array $headers) Returns a new TabulaDataReader with a new set of headers. */ interface TabularDataReader extends Countable, IteratorAggregate { /** * Returns the number of records contained in the tabular data structure * excluding the header record. */ public function count(): int; /** * Returns the tabular data records as an iterator object containing flat array. * * Each record is represented as a simple array containing strings or null values. * * If the CSV document has a header record then each record is combined * to the header record and the header record is removed from the iterator. * * If the CSV document is inconsistent. Missing record fields are * filled with null values while extra record fields are strip from * the returned object. * * @return Iterator<array-key, array<array-key, mixed>> */ public function getIterator(): Iterator; /** * Returns the header associated with the tabular data. * * The header must contain unique string or to be an empty array * if no header was specified. * * @return array<string> */ public function getHeader(): array; /** * Returns the tabular data records as an iterator object. * * Each record is represented as a simple array containing strings or null values. * * If the tabular data has a header record then each record is combined * to the header record and the header record is removed from the iterator. * * If the tabular data is inconsistent. Missing record fields are * filled with null values while extra record fields are strip from * the returned object. * * @param array<string> $header an optional header mapper to use instead of the CSV document header * * @return Iterator<array-key, array<array-key, mixed>> */ public function getRecords(array $header = []): Iterator; /** * Returns the next key-value pairs from the tabular data (first * column is the key, second column is the value). * * By default, if no column index is provided: * - the first column is used to provide the keys * - the second column is used to provide the value * * @param string|int $offset_index The column index to serve as offset * @param string|int $value_index The column index to serve as value * * @throws UnableToProcessCsv if the column index is invalid or not found */ public function fetchPairs($offset_index = 0, $value_index = 1): Iterator; /** * DEPRECATION WARNING! This class will be removed in the next major point release. * * @deprecated since version 9.9.0 * * Returns the nth record from the tabular data. * * By default, if no index is provided the first record of the tabular data is returned * * @param int $nth_record the tabular data record offset * * @throws UnableToProcessCsv if argument is less than 0 */ public function fetchOne(int $nth_record = 0): array; /** * DEPRECATION WARNING! This class will be removed in the next major point release. * * @deprecated since version 9.8.0 * * @see ::fetchColumnByName * @see ::fetchColumnByOffset * * Returns a single column from the next record of the tabular data. * * By default, if no value is supplied the first column is fetched * * @param string|int $index CSV column index * * @throws UnableToProcessCsv if the column index is invalid or not found * * @return Iterator<int, mixed> */ public function fetchColumn(string|int $index = 0): Iterator; } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\Csv; /** * Defines constants for common BOM sequences. */ interface ByteSequence { public const BOM_UTF8 = "\xEF\xBB\xBF"; public const BOM_UTF16_BE = "\xFE\xFF"; public const BOM_UTF16_LE = "\xFF\xFE"; public const BOM_UTF32_BE = "\x00\x00\xFE\xFF"; public const BOM_UTF32_LE = "\xFF\xFE\x00\x00"; } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use InvalidArgumentException; use php_user_filter; use function array_map; use function in_array; use function str_replace; use function strcspn; use function stream_bucket_append; use function stream_bucket_make_writeable; use function stream_filter_register; use function stream_get_filters; use function strlen; /** * A stream filter to improve enclosure character usage. * * DEPRECATION WARNING! This class will be removed in the next major point release * * @deprecated since version 9.10.0 * @see Writer::forceEnclosure() * * @see https://tools.ietf.org/html/rfc4180#section-2 * @see https://bugs.php.net/bug.php?id=38301 */ class EncloseField extends php_user_filter { public const FILTERNAME = 'convert.league.csv.enclosure'; /** Default sequence. */ protected string $sequence; /** Characters that triggers enclosure in PHP. */ protected static string $force_enclosure = "\n\r\t "; /** * Static method to return the stream filter filtername. */ public static function getFiltername(): string { return self::FILTERNAME; } /** * Static method to register the class as a stream filter. */ public static function register(): void { if (!in_array(self::FILTERNAME, stream_get_filters(), true)) { stream_filter_register(self::FILTERNAME, self::class); } } /** * Static method to add the stream filter to a {@link Writer} object. * * @throws InvalidArgumentException if the sequence is malformed * @throws Exception */ public static function addTo(Writer $csv, string $sequence): Writer { self::register(); if (!self::isValidSequence($sequence)) { throw new InvalidArgumentException('The sequence must contain at least one character to force enclosure'); } return $csv ->addFormatter(fn (array $record): array => array_map(fn (?string $value): string => $sequence.$value, $record)) ->addStreamFilter(self::FILTERNAME, ['sequence' => $sequence]); } /** * Filter type and sequence parameters. * * The sequence to force enclosure MUST contain one of the following character ("\n\r\t ") */ protected static function isValidSequence(string $sequence): bool { return strlen($sequence) !== strcspn($sequence, self::$force_enclosure); } public function onCreate(): bool { return is_array($this->params) && isset($this->params['sequence']) && self::isValidSequence($this->params['sequence']); } /** * @param resource $in * @param resource $out * @param int $consumed */ public function filter($in, $out, &$consumed, bool $closing): int { /** @var array $params */ $params = $this->params; /** @var string $sequence */ $sequence = $params['sequence']; while (null !== ($bucket = stream_bucket_make_writeable($in))) { $bucket->data = str_replace($sequence, '', $bucket->data); $consumed += $bucket->datalen; stream_bucket_append($out, $bucket); } return PSFS_PASS_ON; } } <?php /** * League.Csv (https://csv.thephpleague.com) * * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\Csv; use ArrayIterator; use CallbackFilterIterator; use Closure; use Generator; use Iterator; use JsonSerializable; use League\Csv\Serializer\Denormalizer; use League\Csv\Serializer\MappingFailed; use League\Csv\Serializer\TypeCastingFailed; use LimitIterator; use function array_filter; use function array_flip; use function array_search; use function is_string; use function iterator_count; /** * Represents the result set of a {@link Reader} processed by a {@link Statement}. */ class ResultSet implements TabularDataReader, JsonSerializable { /** @var array<string> */ protected array $header; /* @var Iterator<array-key, array<array-key, mixed>> */ protected Iterator $records; /** * @param Iterator|array<array-key, array<array-key, mixed>> $records * @param array<string> $header * * @throws SyntaxError */ public function __construct(Iterator|array $records, array $header = []) { if ($header !== array_filter($header, is_string(...))) { throw SyntaxError::dueToInvalidHeaderColumnNames(); } $this->header = array_values($this->validateHeader($header)); $this->records = match (true) { $records instanceof Iterator => $records, default => new ArrayIterator($records), }; } /** * @throws SyntaxError if the header syntax is invalid */ protected function validateHeader(array $header): array { return match (true) { $header !== array_unique($header) => throw SyntaxError::dueToDuplicateHeaderColumnNames($header), [] !== array_filter(array_keys($header), fn (string|int $value) => !is_int($value) || $value < 0) => throw new SyntaxError('The header mapper indexes should only contain positive integer or 0.'), default => $header, }; } public function __destruct() { unset($this->records); } /** * Returns a new instance from an object implementing the TabularDataReader interface. * * @throws SyntaxError */ public static function createFromTabularDataReader(TabularDataReader $reader): self { return new self($reader->getRecords(), $reader->getHeader()); } /** * Returns a new instance from a collection without header.. * * @throws SyntaxError */ public static function createFromRecords(Iterator|array $records = []): self { return new self($records); } /** * Returns the header associated with the result set. * * @return array<string> */ public function getHeader(): array { return $this->header; } /** * @throws SyntaxError */ public function getIterator(): Iterator { return $this->getRecords(); } /** * @param Closure(array<mixed>, array-key=): mixed $closure */ public function each(Closure $closure): bool { foreach ($this as $offset => $record) { if (false === $closure($record, $offset)) { return false; } } return true; } /** * @param Closure(array<mixed>, array-key=): bool $closure */ public function exists(Closure $closure): bool { foreach ($this as $offset => $record) { if (true === $closure($record, $offset)) { return true; } } return false; } /** * @param Closure(TInitial|null, array<mixed>, array-key=): TInitial $closure * @param TInitial|null $initial * * @template TInitial * * @return TInitial|null */ public function reduce(Closure $closure, mixed $initial = null): mixed { foreach ($this as $offset => $record) { $initial = $closure($initial, $record, $offset); } return $initial; } /** * @param positive-int $recordsCount * * @throws InvalidArgument * * @return iterable<TabularDataReader> */ public function chunkBy(int $recordsCount): iterable { if ($recordsCount < 1) { throw InvalidArgument::dueToInvalidChunkSize($recordsCount, __METHOD__); } $header = $this->getHeader(); $records = []; $nbRecords = 0; foreach ($this->getRecords() as $record) { $records[] = $record; ++$nbRecords; if ($nbRecords === $recordsCount) { yield new self($records, $header); } } if ([] !== $records) { yield new self($records, $header); } } /** * @param array<string> $headers */ public function mapHeader(array $headers): TabularDataReader { return Statement::create()->process($this, $headers); } public function filter(Closure $closure): TabularDataReader { return Statement::create()->where($closure)->process($this); } public function slice(int $offset, int $length = null): TabularDataReader { return Statement::create()->offset($offset)->limit($length ?? -1)->process($this); } public function sorted(Closure $orderBy): TabularDataReader { return Statement::create()->orderBy($orderBy)->process($this); } public function select(string|int ...$columns): TabularDataReader { return Statement::create()->select(...$columns)->process($this); } public function matching(string $expression): iterable { return FragmentFinder::create()->findAll($expression, $this); } public function matchingFirst(string $expression): ?TabularDataReader { return FragmentFinder::create()->findFirst($expression, $this); } /** * @throws SyntaxError * @throws FragmentNotFound */ public function matchingFirstOrFail(string $expression): TabularDataReader { return FragmentFinder::create()->findFirstOrFail($expression, $this); } /** * @param array<string> $header * * @throws SyntaxError * * @return Iterator<array-key, array<array-key, mixed>> */ public function getRecords(array $header = []): Iterator { return $this->combineHeader($this->prepareHeader($header)); } /** * @template T of object * @param class-string<T> $className * @param array<string> $header * * @throws Exception * @throws MappingFailed * @throws TypeCastingFailed * @return iterator<T> */ public function getRecordsAsObject(string $className, array $header = []): Iterator { $header = $this->prepareHeader($header); return Denormalizer::assignAll( $className, $this->combineHeader($header), $header ); } /** * @param array<string> $header * * @throws SyntaxError * @return array<string> */ protected function prepareHeader(array $header): array { if ($header !== array_filter($header, is_string(...))) { throw SyntaxError::dueToInvalidHeaderColumnNames(); } $header = $this->validateHeader($header); if ([] === $header) { $header = $this->header; } return $header; } /** * Combines the header to each record if present. * * @param array<array-key, string|int> $header * * @return Iterator<array-key, array<array-key, mixed>> */ protected function combineHeader(array $header): Iterator { return match (true) { [] === $header => $this->records, default => new MapIterator($this->records, function (array $record) use ($header): array { $assocRecord = []; $row = array_values($record); foreach ($header as $offset => $headerName) { $assocRecord[$headerName] = $row[$offset] ?? null; } return $assocRecord; }), }; } public function count(): int { return iterator_count($this->records); } public function jsonSerialize(): array { return array_values([...$this->records]); } public function first(): array { return $this->nth(0); } public function value(int|string $column = 0): mixed { return match (true) { is_string($column) => $this->first()[$column] ?? null, default => array_values($this->first())[$column] ?? null, }; } public function nth(int $nth_record): array { if ($nth_record < 0) { throw InvalidArgument::dueToInvalidRecordOffset($nth_record, __METHOD__); } $iterator = new LimitIterator($this->getIterator(), $nth_record, 1); $iterator->rewind(); /** @var array|null $result */ $result = $iterator->current(); return $result ?? []; } /** * @param class-string $className * * @throws InvalidArgument */ public function nthAsObject(int $nth, string $className, array $header = []): ?object { $header = $this->prepareHeader($header); $record = $this->nth($nth); if ([] === $record) { return null; } if ([] === $header || $this->header === $header) { return Denormalizer::assign($className, $record); } $row = array_values($record); $record = []; foreach ($header as $offset => $headerName) { $record[$headerName] = $row[$offset] ?? null; } return Denormalizer::assign($className, $record); } /** * @param class-string $className * * @throws InvalidArgument */ public function firstAsObject(string $className, array $header = []): ?object { return $this->nthAsObject(0, $className, $header); } public function fetchColumn(string|int $index = 0): Iterator { return $this->yieldColumn( $this->getColumnIndex($index, 'offset', __METHOD__) ); } /** * @throws Exception */ public function fetchColumnByName(string $name): Iterator { return $this->yieldColumn( $this->getColumnIndexByValue($name, 'name', __METHOD__) ); } /** * @throws Exception */ public function fetchColumnByOffset(int $offset): Iterator { return $this->yieldColumn( $this->getColumnIndexByKey($offset, 'offset', __METHOD__) ); } protected function yieldColumn(string|int $offset): Generator { yield from new MapIterator( new CallbackFilterIterator($this->records, fn (array $record): bool => isset($record[$offset])), fn (array $record): string => $record[$offset] ); } /** * Filters a column name against the header if any. * * @throws InvalidArgument if the field is invalid or not found */ protected function getColumnIndex(string|int $field, string $type, string $method): string|int { return match (true) { is_string($field) => $this->getColumnIndexByValue($field, $type, $method), default => $this->getColumnIndexByKey($field, $type, $method), }; } /** * Returns the selected column name. * * @throws InvalidArgument if the column is not found */ protected function getColumnIndexByValue(string $value, string $type, string $method): string { return match (true) { false === array_search($value, $this->header, true) => throw InvalidArgument::dueToInvalidColumnIndex($value, $type, $method), default => $value, }; } /** * Returns the selected column name according to its offset. * * @throws InvalidArgument if the field is invalid or not found */ protected function getColumnIndexByKey(int $index, string $type, string $method): int|string { return match (true) { $index < 0 => throw InvalidArgument::dueToInvalidColumnIndex($index, $type, $method), [] === $this->header => $index, false !== ($value = array_search($index, array_flip($this->header), true)) => $value, default => throw InvalidArgument::dueToInvalidColumnIndex($index, $type, $method), }; } public function fetchPairs($offset_index = 0, $value_index = 1): Iterator { $offset = $this->getColumnIndex($offset_index, 'offset', __METHOD__); $value = $this->getColumnIndex($value_index, 'value', __METHOD__); $iterator = new MapIterator( new CallbackFilterIterator($this->records, fn (array $record): bool => isset($record[$offset])), fn (array $record): array => [$record[$offset], $record[$value] ?? null] ); /** @var array{0:int|string, 1:string|null} $pair */ foreach ($iterator as $pair) { yield $pair[0] => $pair[1]; } } /** * DEPRECATION WARNING! This method will be removed in the next major point release. * * @see ResultSet::nth() * @deprecated since version 9.9.0 * @codeCoverageIgnore */ public function fetchOne(int $nth_record = 0): array { return $this->nth($nth_record); } /** * DEPRECATION WARNING! This method will be removed in the next major point release. * * @see Reader::getRecordsAsObject() * @deprecated Since version 9.15.0 * @codeCoverageIgnore * * @param class-string $className * @param array<string> $header * * @throws Exception * @throws MappingFailed * @throws TypeCastingFailed */ public function getObjects(string $className, array $header = []): Iterator { return $this->getRecordsAsObject($className, $header); } } <?php /* * This file is part of Composer. * * (c) Nils Adermann <naderman@naderman.de> * Jordi Boggiano <j.boggiano@seld.be> * * 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 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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null */ private static $installed; /** * @var bool|null */ private static $canGetVendors; /** * @var array[] * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}> */ 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<string> */ 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<string> */ 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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} */ 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<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}> */ 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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data */ public static function reload($data) { self::$installed = $data; self::$installedByVendor = array(); } /** * @return array[] * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}> */ private static function getInstalled() { if (null === self::$canGetVendors) { self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); } $installed = array(); if (self::$canGetVendors) { foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { 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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */ $required = require $vendorDir.'/composer/installed.php'; $installed[] = self::$installedByVendor[$vendorDir] = $required; if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { self::$installed = $installed[count($installed) - 1]; } } } } 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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */ $required = require __DIR__ . '/installed.php'; self::$installed = $required; } else { self::$installed = array(); } } if (self::$installed !== array()) { $installed[] = self::$installed; } return $installed; } } 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. <?php // autoload_classmap.php @generated by Composer $vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', ); <?php // autoload_namespaces.php @generated by Composer $vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( ); { "packages": [ { "name": "league/csv", "version": "9.15.0", "version_normalized": "9.15.0.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", "reference": "fa7e2441c0bc9b2360f4314fd6c954f7ff40d435" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/thephpleague/csv/zipball/fa7e2441c0bc9b2360f4314fd6c954f7ff40d435", "reference": "fa7e2441c0bc9b2360f4314fd6c954f7ff40d435", "shasum": "" }, "require": { "ext-filter": "*", "ext-json": "*", "ext-mbstring": "*", "php": "^8.1.2" }, "require-dev": { "doctrine/collections": "^2.1.4", "ext-dom": "*", "ext-xdebug": "*", "friendsofphp/php-cs-fixer": "^v3.22.0", "phpbench/phpbench": "^1.2.15", "phpstan/phpstan": "^1.10.57", "phpstan/phpstan-deprecation-rules": "^1.1.4", "phpstan/phpstan-phpunit": "^1.3.15", "phpstan/phpstan-strict-rules": "^1.5.2", "phpunit/phpunit": "^10.5.9", "symfony/var-dumper": "^6.4.2" }, "suggest": { "ext-dom": "Required to use the XMLConverter and the HTMLConverter classes", "ext-iconv": "Needed to ease transcoding CSV using iconv stream filters" }, "time": "2024-02-20T20:00:00+00:00", "type": "library", "extra": { "branch-alias": { "dev-master": "9.x-dev" } }, "installation-source": "dist", "autoload": { "files": [ "src/functions_include.php" ], "psr-4": { "League\\Csv\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Ignace Nyamagana Butera", "email": "nyamsprod@gmail.com", "homepage": "https://github.com/nyamsprod/", "role": "Developer" } ], "description": "CSV data manipulation made easy in PHP", "homepage": "https://csv.thephpleague.com", "keywords": [ "convert", "csv", "export", "filter", "import", "read", "transform", "write" ], "support": { "docs": "https://csv.thephpleague.com", "issues": "https://github.com/thephpleague/csv/issues", "rss": "https://github.com/thephpleague/csv/releases.atom", "source": "https://github.com/thephpleague/csv" }, "funding": [ { "url": "https://github.com/sponsors/nyamsprod", "type": "github" } ], "install-path": "../league/csv" } ], "dev": true, "dev-package-names": [] } <?php // autoload_files.php @generated by Composer $vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( '9e4824c5afbdc1482b6025ce3d4dfde8' => $vendorDir . '/league/csv/src/functions_include.php', ); <?php // autoload_psr4.php @generated by Composer $vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( 'League\\Csv\\' => array($vendorDir . '/league/csv/src'), ); <?php // autoload_real.php @generated by Composer class ComposerAutoloaderInitcf8212b08cc6e1d25da57aa1d364a1fa { private static $loader; public static function loadClassLoader($class) { if ('Composer\Autoload\ClassLoader' === $class) { require __DIR__ . '/ClassLoader.php'; } } /** * @return \Composer\Autoload\ClassLoader */ public static function getLoader() { if (null !== self::$loader) { return self::$loader; } require __DIR__ . '/platform_check.php'; spl_autoload_register(array('ComposerAutoloaderInitcf8212b08cc6e1d25da57aa1d364a1fa', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); spl_autoload_unregister(array('ComposerAutoloaderInitcf8212b08cc6e1d25da57aa1d364a1fa', 'loadClassLoader')); require __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInitcf8212b08cc6e1d25da57aa1d364a1fa::getInitializer($loader)); $loader->register(true); $filesToLoad = \Composer\Autoload\ComposerStaticInitcf8212b08cc6e1d25da57aa1d364a1fa::$files; $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; require $file; } }, null, null); foreach ($filesToLoad as $fileIdentifier => $file) { $requireFile($fileIdentifier, $file); } return $loader; } } <?php return array( 'root' => array( 'name' => '__root__', 'pretty_version' => '1.0.0+no-version-set', 'version' => '1.0.0.0', 'reference' => NULL, 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev' => true, ), 'versions' => array( '__root__' => array( 'pretty_version' => '1.0.0+no-version-set', 'version' => '1.0.0.0', 'reference' => NULL, 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => false, ), 'league/csv' => array( 'pretty_version' => '9.15.0', 'version' => '9.15.0.0', 'reference' => 'fa7e2441c0bc9b2360f4314fd6c954f7ff40d435', 'type' => 'library', 'install_path' => __DIR__ . '/../league/csv', 'aliases' => array(), 'dev_requirement' => false, ), ), ); <?php /* * This file is part of Composer. * * (c) Nils Adermann <naderman@naderman.de> * Jordi Boggiano <j.boggiano@seld.be> * * 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 <fabien@symfony.com> * @author Jordi Boggiano <j.boggiano@seld.be> * @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<string, array<string, int>> */ private $prefixLengthsPsr4 = array(); /** * @var array<string, list<string>> */ private $prefixDirsPsr4 = array(); /** * @var list<string> */ 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<string, array<string, list<string>>> */ private $prefixesPsr0 = array(); /** * @var list<string> */ private $fallbackDirsPsr0 = array(); /** @var bool */ private $useIncludePath = false; /** * @var array<string, string> */ private $classMap = array(); /** @var bool */ private $classMapAuthoritative = false; /** * @var array<string, bool> */ private $missingClasses = array(); /** @var string|null */ private $apcuPrefix; /** * @var array<string, self> */ private static $registeredLoaders = array(); /** * @param string|null $vendorDir */ public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; self::initializeIncludeClosure(); } /** * @return array<string, list<string>> */ public function getPrefixes() { if (!empty($this->prefixesPsr0)) { return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); } return array(); } /** * @return array<string, list<string>> */ public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } /** * @return list<string> */ public function getFallbackDirs() { return $this->fallbackDirsPsr0; } /** * @return list<string> */ public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } /** * @return array<string, string> Array of classname => path */ public function getClassMap() { return $this->classMap; } /** * @param array<string, string> $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>|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>|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>|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>|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<string, self> */ 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); } } <?php // autoload_static.php @generated by Composer namespace Composer\Autoload; class ComposerStaticInitcf8212b08cc6e1d25da57aa1d364a1fa { public static $files = array ( '9e4824c5afbdc1482b6025ce3d4dfde8' => __DIR__ . '/..' . '/league/csv/src/functions_include.php', ); public static $prefixLengthsPsr4 = array ( 'L' => array ( 'League\\Csv\\' => 11, ), ); public static $prefixDirsPsr4 = array ( 'League\\Csv\\' => array ( 0 => __DIR__ . '/..' . '/league/csv/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 = ComposerStaticInitcf8212b08cc6e1d25da57aa1d364a1fa::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInitcf8212b08cc6e1d25da57aa1d364a1fa::$prefixDirsPsr4; $loader->classMap = ComposerStaticInitcf8212b08cc6e1d25da57aa1d364a1fa::$classMap; }, null, ClassLoader::class); } } <?php // platform_check.php @generated by Composer $issues = array(); if (!(PHP_VERSION_ID >= 80102)) { $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.2". 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; } } trigger_error( 'Composer detected issues in your platform: ' . implode(' ', $issues), E_USER_ERROR ); } /.git /.php-cs-fixer.cache /vendor /tags { "require": { "league/csv": "^9.15" } } FROM deblan/php:8.3 WORKDIR /app COPY . . RUN composer install ENTRYPOINT ["php", "index.php"] { "_readme": [ "This file locks the dependencies of your project to a known state", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], "content-hash": "32c0d1088b6701edd94e450950a72a8f", "packages": [ { "name": "league/csv", "version": "9.15.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", "reference": "fa7e2441c0bc9b2360f4314fd6c954f7ff40d435" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/thephpleague/csv/zipball/fa7e2441c0bc9b2360f4314fd6c954f7ff40d435", "reference": "fa7e2441c0bc9b2360f4314fd6c954f7ff40d435", "shasum": "" }, "require": { "ext-filter": "*", "ext-json": "*", "ext-mbstring": "*", "php": "^8.1.2" }, "require-dev": { "doctrine/collections": "^2.1.4", "ext-dom": "*", "ext-xdebug": "*", "friendsofphp/php-cs-fixer": "^v3.22.0", "phpbench/phpbench": "^1.2.15", "phpstan/phpstan": "^1.10.57", "phpstan/phpstan-deprecation-rules": "^1.1.4", "phpstan/phpstan-phpunit": "^1.3.15", "phpstan/phpstan-strict-rules": "^1.5.2", "phpunit/phpunit": "^10.5.9", "symfony/var-dumper": "^6.4.2" }, "suggest": { "ext-dom": "Required to use the XMLConverter and the HTMLConverter classes", "ext-iconv": "Needed to ease transcoding CSV using iconv stream filters" }, "type": "library", "extra": { "branch-alias": { "dev-master": "9.x-dev" } }, "autoload": { "files": [ "src/functions_include.php" ], "psr-4": { "League\\Csv\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Ignace Nyamagana Butera", "email": "nyamsprod@gmail.com", "homepage": "https://github.com/nyamsprod/", "role": "Developer" } ], "description": "CSV data manipulation made easy in PHP", "homepage": "https://csv.thephpleague.com", "keywords": [ "convert", "csv", "export", "filter", "import", "read", "transform", "write" ], "support": { "docs": "https://csv.thephpleague.com", "issues": "https://github.com/thephpleague/csv/issues", "rss": "https://github.com/thephpleague/csv/releases.atom", "source": "https://github.com/thephpleague/csv" }, "funding": [ { "url": "https://github.com/sponsors/nyamsprod", "type": "github" } ], "time": "2024-02-20T20:00:00+00:00" } ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": [], "platform-dev": [], "plugin-api-version": "2.6.0" } [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true [remote "origin"] url = git@gitnet.fr:deblan/nextcloud_passwords_to_bitwarden.git fetch = +refs/heads/*:refs/remotes/origin/* [branch "main"] remote = origin merge = refs/heads/main [remote "github"] url = git@github.com:simmstein/nextcloud_passwords_to_bitwarden.git fetch = +refs/heads/*:refs/remotes/github/* ceb0b8faeccf55ba8c64c3dc90e0015f8b058a5a ceb0b8faeccf55ba8c64c3dc90e0015f8b058a5a 64a233af5e54a14e24ff031f39fa1d714006c3f8 #!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first one removes the # "# Please enter the commit message..." help message. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. COMMIT_MSG_FILE=$1 COMMIT_SOURCE=$2 SHA1=$3 /usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" # case "$COMMIT_SOURCE,$SHA1" in # ,|template,) # /usr/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; # *) ;; # esac # SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" # if test -z "$COMMIT_SOURCE" # then # /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" # fi #!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. exit 1 } #!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up to date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /usr/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi <<\DOC_END This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". DOC_END #!/bin/sh # # An example hook script to block unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 <ref> <oldrev> <newrev>)" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "usage: $0 <ref> <oldrev> <newrev>" >&2 exit 1 fi # --- Config allowunannotated=$(git config --type=bool hooks.allowunannotated) allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) denycreatebranch=$(git config --type=bool hooks.denycreatebranch) allowdeletetag=$(git config --type=bool hooks.allowdeletetag) allowmodifytag=$(git config --type=bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0') if [ "$newrev" = "$zero" ]; then newrev_type=delete else newrev_type=$(git cat-file -t $newrev) fi case "$refname","$newrev_type" in refs/tags/*,commit) # un-annotated tag short_refname=${refname##refs/tags/} if [ "$allowunannotated" != "true" ]; then echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 #!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info #!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git merge" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message to # stderr if it wants to stop the merge commit. # # To enable this hook, rename this file to "pre-merge-commit". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" : #!/bin/sh # # An example hook script to make use of push options. # The example simply echoes all push options that start with 'echoback=' # and rejects all pushes when the "reject" push option is used. # # To enable this hook, rename this file to "pre-receive". if test -n "$GIT_PUSH_OPTION_COUNT" then i=0 while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" do eval "value=\$GIT_PUSH_OPTION_$i" case "$value" in echoback=*) echo "echo from the pre-receive-hook: ${value#*=}" >&2 ;; reject) exit 1 esac i=$((i + 1)) done fi #!/bin/sh # An example hook script to verify what is about to be pushed. Called by "git # push" after it has checked the remote status, but before anything has been # pushed. If this script exits with a non-zero status nothing will be pushed. # # This hook is called with the following parameters: # # $1 -- Name of the remote to which the push is being done # $2 -- URL to which the push is being done # # If pushing without using a named remote those arguments will be equal. # # Information about the commits which are being pushed is supplied as lines to # the standard input in the form: # # <local ref> <local oid> <remote ref> <remote oid> # # This sample shows how to prevent push of commits where the log message starts # with "WIP" (work in progress). remote="$1" url="$2" zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0') while read local_ref local_oid remote_ref remote_oid do if test "$local_oid" = "$zero" then # Handle delete : else if test "$remote_oid" = "$zero" then # New branch, examine all commits range="$local_oid" else # Update to existing branch, examine new commits range="$remote_oid..$local_oid" fi # Check for WIP commit commit=$(git rev-list -n 1 --grep '^WIP' "$range") if test -n "$commit" then echo >&2 "Found WIP commit in $local_ref, not pushing" exit 1 fi fi done exit 0 #!/bin/sh # # An example hook script to verify what is about to be committed # by applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup precommit="$(git rev-parse --git-path hooks/pre-commit)" test -x "$precommit" && exec "$precommit" ${1+"$@"} : #!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=$(git hash-object -t tree /dev/null) fi # If you want to allow non-ASCII filenames set this variable to true. allownonascii=$(git config --type=bool hooks.allownonascii) # Redirect output to stderr. exec 1>&2 # Cross platform projects tend to avoid non-ASCII filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test $(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 then cat <<\EOF Error: Attempt to add a non-ASCII file name. This can cause problems if you want to work with people on other platforms. To be portable it is advisable to rename the file. If you know what you are doing you can disable this check using: git config hooks.allownonascii true EOF exit 1 fi # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against -- #!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup commitmsg="$(git rev-parse --git-path hooks/commit-msg)" test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} : #!/bin/sh # An example hook script to update a checked-out tree on a git push. # # This hook is invoked by git-receive-pack(1) when it reacts to git # push and updates reference(s) in its repository, and when the push # tries to update the branch that is currently checked out and the # receive.denyCurrentBranch configuration variable is set to # updateInstead. # # By default, such a push is refused if the working tree and the index # of the remote repository has any difference from the currently # checked out commit; when both the working tree and the index match # the current commit, they are updated to match the newly pushed tip # of the branch. This hook is to be used to override the default # behaviour; however the code below reimplements the default behaviour # as a starting point for convenient modification. # # The hook receives the commit with which the tip of the current # branch is going to be updated: commit=$1 # It can exit with a non-zero status to refuse the push (when it does # so, it must not modify the index or the working tree). die () { echo >&2 "$*" exit 1 } # Or it can make any necessary changes to the working tree and to the # index to bring them to the desired state when the tip of the current # branch is updated to the new commit, and exit with a zero status. # # For example, the hook can simply run git read-tree -u -m HEAD "$1" # in order to emulate git fetch that is run in the reverse direction # with git push, as the two-tree form of git read-tree -u -m is # essentially the same as git switch or git checkout that switches # branches while keeping the local changes in the working tree that do # not interfere with the difference between the branches. # The below is a more-or-less exact translation to shell of the C code # for the default behaviour for git's push-to-checkout hook defined in # the push_to_deploy() function in builtin/receive-pack.c. # # Note that the hook will be executed from the repository directory, # not from the working tree, so if you want to perform operations on # the working tree, you will have to adapt your code accordingly, e.g. # by adding "cd .." or using relative paths. if ! git update-index -q --ignore-submodules --refresh then die "Up-to-date check failed" fi if ! git diff-files --quiet --ignore-submodules -- then die "Working directory has unstaged changes" fi # This is a rough translation of: # # head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX if git cat-file -e HEAD 2>/dev/null then head=HEAD else head=$(git hash-object -t tree --stdin </dev/null) fi if ! git diff-index --quiet --cached --ignore-submodules $head -- then die "Working directory has staged changes" fi if ! git read-tree -u -m "$commit" then die "Could not update working tree to new HEAD" fi #!/usr/bin/perl use strict; use warnings; use IPC::Open2; # An example hook script to integrate Watchman # (https://facebook.github.io/watchman/) with git to speed up detecting # new and modified files. # # The hook is passed a version (currently 2) and last update token # formatted as a string and outputs to stdout a new update token and # all files that have been modified since the update token. Paths must # be relative to the root of the working tree and separated by a single NUL. # # To enable this hook, rename this file to "query-watchman" and set # 'git config core.fsmonitor .git/hooks/query-watchman' # my ($version, $last_update_token) = @ARGV; # Uncomment for debugging # print STDERR "$0 $version $last_update_token\n"; # Check the hook interface version if ($version ne 2) { die "Unsupported query-fsmonitor hook version '$version'.\n" . "Falling back to scanning...\n"; } my $git_work_tree = get_working_dir(); my $retry = 1; my $json_pkg; eval { require JSON::XS; $json_pkg = "JSON::XS"; 1; } or do { require JSON::PP; $json_pkg = "JSON::PP"; }; launch_watchman(); sub launch_watchman { my $o = watchman_query(); if (is_work_tree_watched($o)) { output_result($o->{clock}, @{$o->{files}}); } } sub output_result { my ($clockid, @files) = @_; # Uncomment for debugging watchman output # open (my $fh, ">", ".git/watchman-output.out"); # binmode $fh, ":utf8"; # print $fh "$clockid\n@files\n"; # close $fh; binmode STDOUT, ":utf8"; print $clockid; print "\0"; local $, = "\0"; print @files; } sub watchman_clock { my $response = qx/watchman clock "$git_work_tree"/; die "Failed to get clock id on '$git_work_tree'.\n" . "Falling back to scanning...\n" if $? != 0; return $json_pkg->new->utf8->decode($response); } sub watchman_query { my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') or die "open2() failed: $!\n" . "Falling back to scanning...\n"; # In the query expression below we're asking for names of files that # changed since $last_update_token but not from the .git folder. # # To accomplish this, we're using the "since" generator to use the # recency index to select candidate nodes and "fields" to limit the # output to file names only. Then we're using the "expression" term to # further constrain the results. if (substr($last_update_token, 0, 1) eq "c") { $last_update_token = "\"$last_update_token\""; } my $query = <<" END"; ["query", "$git_work_tree", { "since": $last_update_token, "fields": ["name"], "expression": ["not", ["dirname", ".git"]] }] END # Uncomment for debugging the watchman query # open (my $fh, ">", ".git/watchman-query.json"); # print $fh $query; # close $fh; print CHLD_IN $query; close CHLD_IN; my $response = do {local $/; <CHLD_OUT>}; # Uncomment for debugging the watch response # open ($fh, ">", ".git/watchman-response.json"); # print $fh $response; # close $fh; die "Watchman: command returned no output.\n" . "Falling back to scanning...\n" if $response eq ""; die "Watchman: command returned invalid output: $response\n" . "Falling back to scanning...\n" unless $response =~ /^\{/; return $json_pkg->new->utf8->decode($response); } sub is_work_tree_watched { my ($output) = @_; my $error = $output->{error}; if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { $retry--; my $response = qx/watchman watch "$git_work_tree"/; die "Failed to make watchman watch '$git_work_tree'.\n" . "Falling back to scanning...\n" if $? != 0; $output = $json_pkg->new->utf8->decode($response); $error = $output->{error}; die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; # Uncomment for debugging watchman output # open (my $fh, ">", ".git/watchman-output.out"); # close $fh; # Watchman will always return all files on the first query so # return the fast "everything is dirty" flag to git and do the # Watchman query just to get it over with now so we won't pay # the cost in git to look up each individual file. my $o = watchman_clock(); $error = $output->{error}; die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; output_result($o->{clock}, ("/")); $last_update_token = $o->{clock}; eval { launch_watchman() }; return 0; } die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; return 1; } sub get_working_dir { my $working_dir; if ($^O =~ 'msys' || $^O =~ 'cygwin') { $working_dir = Win32::GetCwd(); $working_dir =~ tr/\\/\//; } else { require Cwd; $working_dir = Cwd::cwd(); } return $working_dir; } # git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ update documentation ref: refs/heads/main DIRC������e()e()M��y���������)2<\K U:6F� .dockerignore�����eW/eWB��y���������c3i"t[ lt Fׁ� Dockerfile��������eܡ`eܡ#��y��������pevYE^א"� README.md�e߁!)xe߁!)x��y`���������9٧\V}v™� composer.json�����ekg$ekg��y�������� 2 sv,:AV� index.php�TREE����5 0 L{ &"f#!,.VhhG!DcRrgH!�0000000000000000000000000000000000000000 9e617a6f1f5d515134a18733beec733798577bef Simon Vieille <simon@deblan.fr> 1709207343 +0100 commit (initial): init 9e617a6f1f5d515134a18733beec733798577bef 65894d22eb13c47273927bb96d42594a217a92f6 Simon Vieille <simon@deblan.fr> 1709208236 +0100 commit: update readme 65894d22eb13c47273927bb96d42594a217a92f6 feb1e6102615ddc40abb6bd34a0d9a1862692e48 Simon Vieille <simon@deblan.fr> 1709212606 +0100 commit: update readme feb1e6102615ddc40abb6bd34a0d9a1862692e48 b4addbee94395a367ff92c958c83b17d3fd8cf0c Simon Vieille <simon@deblan.fr> 1709212654 +0100 commit: update readme b4addbee94395a367ff92c958c83b17d3fd8cf0c a4120c8e7322215a2f47f0ce4e417147364f35f4 Simon Vieille <simon@deblan.fr> 1709220577 +0100 commit: apply prettier a4120c8e7322215a2f47f0ce4e417147364f35f4 19614200237e55cab4ace007c613909a61a1e939 Simon Vieille <simon@deblan.fr> 1709479958 +0100 commit: add docker 19614200237e55cab4ace007c613909a61a1e939 64a233af5e54a14e24ff031f39fa1d714006c3f8 Simon Vieille <simon@deblan.fr> 1709481368 +0100 commit: update documentation 64a233af5e54a14e24ff031f39fa1d714006c3f8 ceb0b8faeccf55ba8c64c3dc90e0015f8b058a5a Simon Vieille <simon@deblan.fr> 1709497517 +0100 commit: update documentation 0000000000000000000000000000000000000000 9e617a6f1f5d515134a18733beec733798577bef Simon Vieille <simon@deblan.fr> 1709207358 +0100 update by push 9e617a6f1f5d515134a18733beec733798577bef 65894d22eb13c47273927bb96d42594a217a92f6 Simon Vieille <simon@deblan.fr> 1709208253 +0100 update by push 65894d22eb13c47273927bb96d42594a217a92f6 feb1e6102615ddc40abb6bd34a0d9a1862692e48 Simon Vieille <simon@deblan.fr> 1709212611 +0100 update by push feb1e6102615ddc40abb6bd34a0d9a1862692e48 b4addbee94395a367ff92c958c83b17d3fd8cf0c Simon Vieille <simon@deblan.fr> 1709212655 +0100 update by push b4addbee94395a367ff92c958c83b17d3fd8cf0c a4120c8e7322215a2f47f0ce4e417147364f35f4 Simon Vieille <simon@deblan.fr> 1709220581 +0100 update by push a4120c8e7322215a2f47f0ce4e417147364f35f4 19614200237e55cab4ace007c613909a61a1e939 Simon Vieille <simon@deblan.fr> 1709479968 +0100 update by push 19614200237e55cab4ace007c613909a61a1e939 64a233af5e54a14e24ff031f39fa1d714006c3f8 Simon Vieille <simon@deblan.fr> 1709481374 +0100 update by push 64a233af5e54a14e24ff031f39fa1d714006c3f8 ceb0b8faeccf55ba8c64c3dc90e0015f8b058a5a Simon Vieille <simon@deblan.fr> 1709497522 +0100 update by push 0000000000000000000000000000000000000000 64a233af5e54a14e24ff031f39fa1d714006c3f8 Simon Vieille <simon@deblan.fr> 1709481489 +0100 update by push 0000000000000000000000000000000000000000 9e617a6f1f5d515134a18733beec733798577bef Simon Vieille <simon@deblan.fr> 1709207343 +0100 commit (initial): init 9e617a6f1f5d515134a18733beec733798577bef 65894d22eb13c47273927bb96d42594a217a92f6 Simon Vieille <simon@deblan.fr> 1709208236 +0100 commit: update readme 65894d22eb13c47273927bb96d42594a217a92f6 feb1e6102615ddc40abb6bd34a0d9a1862692e48 Simon Vieille <simon@deblan.fr> 1709212606 +0100 commit: update readme feb1e6102615ddc40abb6bd34a0d9a1862692e48 b4addbee94395a367ff92c958c83b17d3fd8cf0c Simon Vieille <simon@deblan.fr> 1709212654 +0100 commit: update readme b4addbee94395a367ff92c958c83b17d3fd8cf0c a4120c8e7322215a2f47f0ce4e417147364f35f4 Simon Vieille <simon@deblan.fr> 1709220577 +0100 commit: apply prettier a4120c8e7322215a2f47f0ce4e417147364f35f4 19614200237e55cab4ace007c613909a61a1e939 Simon Vieille <simon@deblan.fr> 1709479958 +0100 commit: add docker 19614200237e55cab4ace007c613909a61a1e939 64a233af5e54a14e24ff031f39fa1d714006c3f8 Simon Vieille <simon@deblan.fr> 1709481368 +0100 commit: update documentation 64a233af5e54a14e24ff031f39fa1d714006c3f8 ceb0b8faeccf55ba8c64c3dc90e0015f8b058a5a Simon Vieille <simon@deblan.fr> 1709497517 +0100 commit: update documentation xIF�sW%(,ff71f~yQn9nU'ե*۶� m @ARB&("5GD)ra~c$%YA('RFC)ǞSُX}NUV5M||ۿڰ| R (B.pZ ţ*֖ゃu�Gr0 ֿrpOMU5PSZ;oK It5-1.oM#Tkv/#6<Q6E/1y$_B+ f,<e}$$*EsSΠִ?yo.T˗5z36 xStĞ{ u5 9$qCg8p瑪5*®7 -_RȰKqG]j㊡F=lE E O9Vʄ�eOO V;80O07T|jrig77ژ@"ԓnRUB5k7P(Isq e-E>rֲ0M?2Qq#e_'ChāȮrf@#[h|oQh{~b]b8a/g9gn"xzǞMT/Sz`:-vGVګW8`ePfl4j4nټIw m^UINWrQI{\wC&hH(XOeT)akb琲)i<ۯ`SwOťP%x+)JMU044e040031QrutuMax_\g*긆#Z?m*J-/N-*c|sef23S0Taf^Jj^AF?'_qPrm{%�XH,x+)JMU0`�� ,xɮV�D+u<H騙`wd_O.ԮJ*m/1>A L dB X:!C8ΰW2n9JIđő,C$iʥf )p'Rd~ת;Vz>kmA>|<!R$ű {GxSU%kiWwm)w T%IV +Mײ{Q]:&G,gGg$hs 2p@A"Eʛ~6w6tC䠘޿^O>00<uo=@Cx1o6׶5!+U;Qkv] ֝(i2Iw7[{u!*#"Fe/2~ݑ1pmg_U5YMP}gMZȗ&]]rHL/5rLpmohklKj!8ϧdc&¦>aZD H0ُpFS2a!gt(,wRN. v1:rt JS<�ۑamL 讉 hag~d{@leC(ZPrѺqB1Kݮ#:%TbGI+؅Eת`pIdυ5s|?:98aJ8e?%fs_&mI>2^Ö˛.V6-"ƛlA" ӿ41ڛ:lZ[:e/ŀگTvφ>UnN%؈h-%A>ۼ8'~& oh_LfFa�^YxuRMo1弿b$ 6"du<=~=~6ެy7}|,V C8WZ "ey~J�Sܦ푼wrZpw׆,[,P456@$c >eXbKE;#ߍn7oǁ<~|78ƧH(m^9g;ʉk"آ@"!ؚ[zXD.u5<IFWrs�OAFHetv;`+Oڒ(oPܓtb>QKQF}L=?u!.NM"!qQxE"JҔq.Nʟapx~vM,5SHG لٓۘO/uUZ;=J2<k.UXEmi-D|uet 5&_?K<xKOR01dK,+(M.MˬH-KNLH/KK//IL/�ztxTMo0 ٿp.!6=Cv뀵R b1E4$9G39#5}}fxJL8w +xJ4铨tE 場5*Tddh=<_@1./݇4eq&PZvW0?<E XE10j )We�1,[xE&Fpgi*aV+WD <ЩAll*qI庛ZčЂjtCp )ۢ_PVZCǐ>Ӂg�>aQ4O:2BCŜZƋU wW"ǓnhU2%m`._7q% x,,J(2l ķ,ѻiɝhJF vD:=``fƽ-.,J^cزH=$s!S $/#Qk [L&'|BVd24%4O冢[mD ~k@waC?!>KXf_#CjثA J'˴~ҵTxMI(֍xuSKo1_1\$vAQ"FRUu<+{6 lf{76&pG 9 .4Sh;Y-R{=b TǎӒ_LVOm3iׄ_>.Γp8\֛fV(˵#Z\VLLz%kBk_0"9ݺLH WjӋ$<:8YL|NƎj ~P,yҀGfm K[K.g HKQ_4Ŧ79<\@Z=lH,9cE.pp-hM'lJǻ > NQ-m5$p+>u8:8=*HW&.3Ð~?;o`pq47} 6Ɲ=D&2~t*\C J|0qAi[r;VHI$o;daslsBp<_+; t2NHKD<zxx+)JMU044e040031QrutuMa0<\=1r7AfרPEũEzYy o.VLVf ^*KI+(`0+.ӱuuXnټad� +xuRMo0 ٿp.evhm6X^blhHt׏g 7O${Kӻ K %0Vy.IT&@ e-R!cQs{Gc1K n]`ؐIiC%ϥ␳5riwt)ӏu?Bq 9I>w\ @NYl\RȐ޸ҚP} *p)UOlZ[S<9HeZM ix+ڐ(yKMxx45=' E>rْEk+MtgҤTD{u5^kUΔk"┪&f'}fJ_;%ql%Zz)4޶>8?).; }R.OkʁIx3.Tה/*spIW  ģ-&*AEx+)JMU04a040031QKON-L/Jeods7o,ߒf`*tKIeXh*Sұ%L 9%\nM\]|]rS\f_\zo~䣫.@%e1l|FLXC3YN)xA03/%B LJae󶇽�KexΣXFgS=%Jӭ1dc;%dOhvՑ>[ԩlz? cfB*""exK1a`/dx6aJ slY̰EA#X H`.@ "K5L aO6*� J@ҐWF|s]? \ۻWqMdZȢvb9DŽqd9v]Ԡ#S@1w+n\5?^ExSZG|Ѡf P|J#Vdu�坲 Uk>eefoYMBWZvN~A=b ޽ N3 L6W5}}/30`Mz'RV+خt{y/M'خ%i&B uf Z823m']4A &nW!}e#m$+cl+a b} p}fM}MJWLa˕zNθs1 6}f{;{<TbDS,c/y>V4Aקm _wI%|-;$|CYXg<֬ʯ͒7TEnITPvf#h ԩU Nx=b1Vz`)~1OksusdzC wd}H'Af|P^o']o+}Ƒ)N-1ȓ|$K=UZx+)JMU044e040031QrutuMa:x{-PEũEzYy o.VLVf ^*KI+(`0+.ӱuuXnټad�m09x+)JMU04a040031QKON-L/Jeods7o,ߒf`*tKIeXh*Sұ%L 9%\nM\]|]rS&Kytg;aTQr~nA~qj^Vq~gĄծ.;4쟙 3RR+ 2 9tz[6o{+�s#LDxSγF�뙧;j@_ %�! n,ð-_z/-Y,9rM#!J*J  ",i@y)>I;AKe" <C1s$\JAPH(1<k$e;#I_�J$EerWÉ(鳤a3<p W /g�JZU}ƀj)XnoɂH8ݫ~໺�3Q<j:�oL ;aQ|!uB^°zw;@ 儜MdѴ4L*'}B&u D%&kϬ3G8tNVi^hsd;H؛Je~1f@=TEttrǫbiׯQ 7YR0<͍('8Kg罛pG;pzz swA@Tv[;עV gıRj[85[2K%Rbllv4t𞺖-5&DZ8ޗ/Hwh'So|5hw'|N/Yt2(gZBU/a<Zwcpj[<_TuA?fsq 9ND^ﵨ5JƵ?K1.1.P ~ٚ5wr;۠(us_~>l4<ǖg. !ZM:r~,Tfh(~@Kua*TnVߵbj _}ʗs -1#gtW0qKk22RMx+)JMU04a040031QKON-L/Jeods7o,ߒf`*tKIeXh*Sұ%L 9%\nM\]|]rSk]y{Ž<(9? 8H/8?a3bjWtBL Py)z F|e:VB-= �Lx}VOFsYZJЇVPx d{]sGg 8ͬ+߯U3hX}}Ȳ>@](=Q k݁Xvtn@Q^vunJm-%KJYpn ha|0$yaENs L0:|xћZ0mʿSZ_E5~U׵b K47�tĮC(¢7J`댪'%KɎ]^b@2w:{B0_S8J",j-ȲYS%X"1c$Bex9�"YND|bd*f{BmR?IlTdt&Um6m1t2i F|亩>1W0tO(_Ae Ҿ)AQ3z/ zGMϐ?ʰJYCѦ0G(ܐ2! ܋_.2r)'bBubi"rD˒: $YgBk:ՈxrQk+Q?[{RN(l-/I:ګ#`#aNL a86;:/A+Ei|IV>_x4n{UO4}- w<_is\BrۂVY5^}' XLܭ *%T$Gn ӕqӵF̚53 4Q)|g~.X>_/düGL^ F2[ R>;=y8b=D>"AC�|D DD{ Z(H_Ǘq-t|dY H�%-"q)uq0 26d;Ht N[mߋF! n/0nލA`g/@6^Ç@ATz)j7-u~uP5 k͢qF 9.tp+ʧ ┥(6ntGOt/~㆟bz^m*ŔP7 ZNKynpj_W"g.a z7ԏAo'WRIčM{[v4+iUCnp@5xSF̙;J> %Qfc7 f,^>~/-U%uUՕ? '1 e A# $^TR1рfBI|<r-B!D*lca?7:~_~ trАJdXMQDBh5VKS_ <e9 <GeEb1ql\ 1K*JʧVw_�(Rgs->W 0C.ᡛnܚS}99 W_ D7@V?NNtO2'*4+د|^yt'_Bmu<fޜ.|.!سW 1e5t$Kx_ǫW}pt2kllVjS mEn�{%@MQ %6R8;FJ~Rş)5WmDž�"4C]di3td;5?U!Ezi$a70XW w}Pf]E(qoʆfg xWz\(w^${_/lQm\#8yp)Ų:AOm-TG-|7T7(upfQ"ˋQ!!.}^*80]rE=Y(:O"Eu>\UL.3:3BF%kSjgV{l Sj7.gT(-U7{E|`&+n7OftWXdeEWlxKOR05gR�̢T%+�X0'514U? (gghV^U�%xKORdp UHIMI/(3 v RO,(rTS SH-/N-R+.Ir QVZr�~KxΣV)J.ӕҭ06; lLeUצTN>hJ{A`JÄl" 9R}z#:ؔ1=nFq%#".1)T -d04R"{| |<0{97w@ bh"_GEW eY6punK~ @y26Yd}itv 1\8Ǡt/=4I):>r"Eus^~Bi!cר :i¿)K_U�ddOv# 3Mds}-9^+P0mz?;JiS` nR._-.,pW qv$Sv3/pڪ 4q3}pU9 It݊27{p"ux1=]\H:mA[oټvvrήl`V"baY@kuŁA="YXQδ_~wP%O~ &Fͦ7No_6c]7y3"�㕱;M raPf%[l5C>hRo̝_h:{`C C:7~eaM$kMΨ$Mԁ7&HuVPiWOz$†Paym"5tr66Ø&8u|ɞuFԪRb0!=DyCsaHw?LXZ{% ej=\{@>LE0m+m~#77ĿlC]XAq,qO�AT`x+)JMU044e040031QrutuMaLTb5?v'3w TQr~nA~qj^Vq~gĄծ.;4쟙 3RR+ 2 9tz[6o{+� a.xTM1 y~5\zanJ˪01L : `=Ϭ4 >TJ dOpAX&}ng^0 T (\bZ$]J|S|QDQ>/WF7fJ^`4&Q4qe Ȅ�,Qi3rqGbZ1蓵M%0iXSZP5c N Hʶh[2dAtՀ_b S{t1f!5\lpmJvhU2'm`67q% x,M-J\+2l ķގ,iɝKꇒFsvD*>``zڽlQoS1 _XFP}n n_^[D)li`<;Yy^\TOՆ.^fo>O]{gt@`fQ{1k;jUVAጰ:FaDh:7L&] M5הDhExΣV)%,[ a26y'ґlz$ BcSP<-а$g2OӔ!sg#0:I*9Qwit I.`b2W֨:~\ѽK; 9B$d IX~CZ>W]?h 5_]}DQK1WkEў4լ$BpN7J2UoeѢ\TX{T] `Vψ@oSó-&nj<CSv/#hιR;iu*r ;zٮ6J{dK^ÒW¢Hec\1x.P^ K~<Q2ӭ~~[U#b?;ƀ`kS{: l;^y-Յvkfo[L2`bh͹xOrFz FZ[p⟄ %3ws8˔4AvX2qo 7k1/OC`<_su|&:C8:[9 bl)8,M*mOIX'dنjḙר/K0 f-%:G"ctnϒ^]|بhiJ1Ce/e:M 7";=E79RFˌWb Dm>fNKozZ X7PtVFBa`.e2_McxY[3罵:yTv}ϜnC7 |`vFlyҼGWUx+)JMU044e040031QrutuMavaϵlTQr~nA~qj^Vq~gĄծ.;4쟙 3RR+ 2 9tz[6o{+�.xTn0 _A8aakeRbl(HrGٱ rpH|B^{)jSΪN$^VHi'1"'F@GeBZ3af91Lf1$\EzPTBb=>/+XV$eD.SrL{_&%c![:p}<|u-,ytP1u x+S@(t f)0;pQzBhJ0yOو[F܅Zk>isk6d (4d!p|ʾמBWq we;DbDW#+9ܷ >tx n�,n}S AUzq"Lw Ҋ+gц4cg:vc~Uu8mݨNjKSkJHle_~G08{t8 VFzYVe#N60wdPc+(% ͓ 5,A ~P% nW}8#iw"Kr+ q/s_{ 0Ihϕ9w;Em/1I~=C:hx+)JMU04a040031QKON-L/Jeods7o,ߒf`*tKIeXh*Sұ%L 9%\nM\]|]rS~;X&25gXB%e1l|FLXC3YN)xA03/%B LJae󶇽�2WK_xuSKo1_1\$vX=4*P$ڈ^*8ɮ[dzggқ51WW&pG 9 n4Sh'Y-įR{` ?Tǎ󅉿%s?g*~gӮ eU|S'"qV7 %PHkG3|6 1`KdX׾DcDA n]pc̫iuJEvGF'>'`Gx_)RM<Yi#OBn ɭ-%Le'ͽtint d!A#72+VC}�.KX㦷-*j*f0Int0!smK[` I\"J_]5 ,&" Ol*W).3~?ko09Sz.#?s"|%Y9 i\Nrt=l[Υ=[ܠL |Dw:s{$%B"s{xuS;o0_q{ԢS;d)Ĩ,AaYbBd% g#=C#j W)ٓMW=T^0gcGNvӒ_LVOm3iׄ_>)Γ|8\֛fV_Zf /a BL&pg=ؒ5Y!/EAЃ n]pciEGEvA,F@&>'`G[<:OVȬmᓐ["Crm } ,!`)jY^47: +H>_R I%xqnEi3$Mx7bއoȨE*:XCRR#GWWM/ê i|k2c: icp&| Kp1)&2+wWP[%!0 ZO#5ؒIGM"yݩ' cH]>M'sD{H?xxSɮF̚轕@ӌR^6ӎ `}O.ݩR@'1-,fr(0/ 9r!OEc2W.u7 qݶ]M?_�HS<b8P|YՌ3p5\ ,]o '�jȒ$+TjUF=j$tr~vAR'7%IfײnUdt4s]P5[sVW}u|̥Q]%PLl6hyM7 gi˳H'hN۪( Bj{@Jy}35H;oN+RH˕#e}J ]kdWv>}ab׫q _:0d1 %S/?xAR5HF*`!?kj{$I=ZۃܥT#>Dmwۑ[&ы| Igm٘V:%j*%i<7"|Z 3#ٮ17{PNk,Waǎ|1/]9ln]?8;ea9L> \d?]#&sL+֐I>i _.ɖ6Rt3w+,m TB^.OFd!'nAU4 [iq/+zC*mjÈCqycds1dѣoӶ�OCkO#'CrR Cahٍ^ BJ�?t1¯n5z&DUnnamed repository; edit this file 'description' to name the repository. <?php require __DIR__.'/vendor/autoload.php'; use League\Csv\Reader; function getRecords(string $file): Iterator { $csv = Reader::createFromPath($file, 'r'); $csv->setHeaderOffset(0); return $csv->getRecords(); } function usage(int $exitCode = 0, ?string $error = null): void { $usage = []; if (null !== $error) { $usage[] = $error; $usage[] = ''; } $file = __FILE__; $usage[] = 'Usage'; $usage[] = " php {$file} -h|--help"; $usage[] = " php {$file} /path/to/Folders.csv /path/to/Passwords.csv"; file_put_contents('php://stderr', implode("\n", $usage)); exit($exitCode); } function checkFile(?string $file): void { if (null === $file) { usage(1, 'File not defined'); } if (!is_file($file) || !is_readable($file)) { usage(1, 'File not found or not readable: '.$file); } } foreach ($argv as $value) { if (in_array($value, ['-h', '--help'])) { usage(); } } $folders = $argv[1] ?? null; $passwords = $argv[2] ?? null; checkFile($folders); checkFile($passwords); $datas = [ 'encrypted' => false, 'folders' => [], 'items' => [], ]; foreach (getRecords($folders) as $item) { $datas['folders'][] = [ 'id' => $item['Id'], 'name' => $item['Label'], ]; } foreach (getRecords($passwords) as $item) { $fields = []; if (!empty($item['Custom Fields'])) { $elements = explode("\n", trim($item['Custom Fields'])); if (!empty($elements)) { foreach ($elements as $element) { preg_match('/^(.*), (.*): (.*)$/iU', $element, $match); $fields[] = [ 'name' => $match[1], 'value' => $match[3], 'linkedId' => null, 'type' => match ($match[2]) { 'secret' => 1, default => 0, }, ]; } } } $datas['items'][] = [ 'passwordHistory' => null, 'revisionDate' => null, 'creationDate' => null, 'deletedDate' => null, 'id' => $item['Id'], 'organizationId' => null, 'folderId' => $item['Folder Id'], 'type' => 1, 'reprompt' => 0, 'name' => $item['Label'], 'notes' => $item['Notes'], 'favorite' => 'true' === $item['Favorite'], 'fields' => $fields, 'login' => [ 'fido2Credentials' => [], 'uris' => [[ 'match' => null, 'uri' => $item['Url'], ]], 'username' => $item['Username'], 'password' => $item['Password'], 'topt' => null, ], ]; } echo json_encode($datas); # Nextcloud Passwords to Bitwarden/Vaultwarden This project allows you to convert [Nextcloud Passwords](https://apps.nextcloud.com/apps/passwords) CSV to a [Bitwarden/Vaultwarden](https://github.com/dani-garcia/vaultwarden) JSON file. ## Installation ### From source ```sh git clone https://gitnet.fr/deblan/nextcloud_passwords_to_bitwarden composer install ``` ### Using the Phar ``` wget https://gitnet.fr/deblan/nextcloud_passwords_to_bitwarden/releases/download/2024-03-03/ncpasswords2bitwarden.phar ``` ### Using docker ```sh docker pull deblan/ncpasswords2bitwarden ``` ## How to ### On Nextcloud - Go to "Personal settings" - Set "English (US)" as language - Go to "Passwords" - Click on "More" - Click on "Backup and restore" - Choose "Backup or export" - Choose "Predefined CSV" - Check "Export Passwords" and "Export Folders" - Click on "Export" and download the archive - Unzip the downloaded file ### Convertion #### From source ```sh php index.php /path/to/Folders.csv /path/to/Passwords.csv > bitwarden_passwords.json ``` #### Using the Phar ```sh php ncpasswords2bitwarden.phar /path/to/Folders.csv /path/to/Passwords.csv > bitwarden_passwords.json ``` #### Using docker ``` docker run --rm -v /path/to/extracted_files:/data deblan/ncpasswords2bitwarden /data/Folders.csv /data/Passwords.csv > bitwarden_passwords.json ``` ### On Bitwarden/Vaultwarden - Go to tools - Go to "Import data" - Choose "Bitwarden (json)" as file format - Choose the file "bitwarden_passwords.json" - Click on "Import data" ### Clean up datas - Remove the downloaded archive - Remove extracted files - Remove bitwarden_passwords.json {"php":"8.0.30","version":"3.8.0","indent":" ","lineEnding":"\n","rules":{"align_multiline_comment":true,"array_indentation":true,"blank_line_before_statement":{"statements":["break","case","continue","declare","default","exit","goto","include","include_once","phpdoc","require","require_once","return","switch","throw","try","yield"]},"combine_consecutive_issets":true,"combine_consecutive_unsets":true,"empty_loop_body":true,"escape_implicit_backslashes":true,"explicit_indirect_variable":true,"explicit_string_variable":true,"heredoc_to_nowdoc":true,"method_argument_space":{"on_multiline":"ensure_fully_multiline"},"method_chaining_indentation":true,"multiline_comment_opening_closing":true,"multiline_whitespace_before_semicolons":{"strategy":"new_line_for_chained_calls"},"no_extra_blank_lines":{"tokens":["break","case","continue","curly_brace_block","default","extra","parenthesis_brace_block","return","square_brace_block","switch","throw","use"]},"no_null_property_initialization":true,"no_superfluous_elseif":true,"no_useless_else":true,"no_useless_return":true,"operator_linebreak":{"only_booleans":true},"ordered_class_elements":true,"php_unit_internal_class":true,"php_unit_test_class_requires_covers":true,"phpdoc_add_missing_param_annotation":true,"phpdoc_no_empty_return":true,"phpdoc_order":true,"phpdoc_order_by_value":true,"phpdoc_types_order":true,"phpdoc_var_annotation_correct_order":true,"return_assignment":true,"simple_to_complex_string_variable":true,"single_line_comment_style":true,"array_syntax":true,"backtick_to_shell_exec":true,"binary_operator_spaces":true,"braces":{"allow_single_line_anonymous_class_with_empty_body":true,"allow_single_line_closure":true},"cast_spaces":true,"class_attributes_separation":{"elements":{"method":"one"}},"class_definition":{"single_line":true},"class_reference_name_casing":true,"clean_namespace":true,"concat_space":true,"echo_tag_syntax":true,"empty_loop_condition":true,"fully_qualified_strict_types":true,"function_typehint_space":true,"general_phpdoc_tag_rename":{"replacements":{"inheritDocs":"inheritDoc"}},"include":true,"increment_style":true,"integer_literal_case":true,"lambda_not_used_import":true,"linebreak_after_opening_tag":true,"magic_constant_casing":true,"magic_method_casing":true,"native_function_casing":true,"native_function_type_declaration_casing":true,"no_alias_language_construct_call":true,"no_alternative_syntax":true,"no_binary_string":true,"no_blank_lines_after_phpdoc":true,"no_empty_comment":true,"no_empty_phpdoc":true,"no_empty_statement":true,"no_leading_namespace_whitespace":true,"no_mixed_echo_print":true,"no_multiline_whitespace_around_double_arrow":true,"no_short_bool_cast":true,"no_singleline_whitespace_before_semicolons":true,"no_spaces_around_offset":true,"no_superfluous_phpdoc_tags":{"allow_mixed":true,"allow_unused_params":true},"no_trailing_comma_in_list_call":true,"no_trailing_comma_in_singleline_array":true,"no_trailing_comma_in_singleline_function_call":true,"no_unneeded_control_parentheses":{"statements":["break","clone","continue","echo_print","return","switch_case","yield","yield_from"]},"no_unneeded_curly_braces":{"namespaces":true},"no_unneeded_import_alias":true,"no_unset_cast":true,"no_unused_imports":true,"no_whitespace_before_comma_in_array":true,"normalize_index_brace":true,"object_operator_without_whitespace":true,"ordered_imports":true,"php_unit_fqcn_annotation":true,"php_unit_method_casing":true,"phpdoc_align":true,"phpdoc_annotation_without_dot":true,"phpdoc_indent":true,"phpdoc_inline_tag_normalizer":true,"phpdoc_no_access":true,"phpdoc_no_alias_tag":true,"phpdoc_no_package":true,"phpdoc_no_useless_inheritdoc":true,"phpdoc_return_self_reference":true,"phpdoc_scalar":true,"phpdoc_separation":true,"phpdoc_single_line_var_spacing":true,"phpdoc_summary":true,"phpdoc_tag_type":{"tags":{"inheritDoc":"inline"}},"phpdoc_to_comment":true,"phpdoc_trim":true,"phpdoc_trim_consecutive_blank_line_separation":true,"phpdoc_types":true,"phpdoc_var_without_name":true,"protected_to_private":true,"semicolon_after_instruction":true,"single_class_element_per_statement":true,"single_line_comment_spacing":true,"single_quote":true,"single_space_after_construct":true,"space_after_semicolon":{"remove_in_empty_for_expressions":true},"standardize_increment":true,"standardize_not_equals":true,"switch_continue_to_break":true,"trailing_comma_in_multiline":true,"trim_array_spaces":true,"types_spaces":true,"unary_operator_spaces":true,"whitespace_after_comma_in_array":true,"yoda_style":true,"blank_line_after_opening_tag":true,"compact_nullable_typehint":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"new_with_braces":true,"no_blank_lines_after_class_opening":true,"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"return_type_declaration":true,"short_scalar_cast":true,"single_blank_line_before_namespace":true,"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"visibility_required":true,"blank_line_after_namespace":true,"constant_case":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"no_break_comment":true,"no_closing_tag":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_import_per_statement":true,"single_line_after_imports":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true},"hashes":{"index.php":2005062171}}<?php try { $phar = new \Phar('ncpasswords2bitwarden.phar', 0, 'ncpasswords2bitwarden.phar'); $phar->buildFromDirectory(__DIR__); $phar->setDefaultStub('index.php', 'index.php'); $phar->stopBuffering(); echo 'Phar archive created successfully.', "\n"; } catch (Exception $e) { echo 'Error: ' . $e->getMessage(), "\n"; } �| VNJ?4 LHP5���GBMB